summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Cowgill <jcowgill@debian.org>2018-01-14 19:29:55 +0000
committerJames Cowgill <jcowgill@debian.org>2018-01-14 19:29:55 +0000
commiteeeb829fddefd9f4312cd57eb2bd2b26b7e860a2 (patch)
tree870ae2fe0c96d6fa1e41f24562de10f33442ae13
parent4b6da7ffc977eb0ab985119acec5abdf8b4248a4 (diff)
New upstream version 0.28.0
-rw-r--r--.github/ISSUE_TEMPLATE3
-rw-r--r--.travis.yml5
-rw-r--r--Copyright368
-rw-r--r--DOCS/client-api-changes.rst6
-rw-r--r--DOCS/compile-windows.md40
-rw-r--r--DOCS/contribute.md2
-rw-r--r--DOCS/interface-changes.rst45
-rw-r--r--DOCS/man/af.rst176
-rw-r--r--DOCS/man/input.rst72
-rw-r--r--DOCS/man/javascript.rst6
-rw-r--r--DOCS/man/lua.rst42
-rw-r--r--DOCS/man/mpv.rst18
-rw-r--r--DOCS/man/options.rst624
-rw-r--r--DOCS/man/stats.rst162
-rw-r--r--DOCS/man/vf.rst317
-rw-r--r--DOCS/man/vo.rst49
-rw-r--r--DOCS/mplayer-changes.rst6
-rw-r--r--LICENSE.GPL (renamed from LICENSE)0
-rw-r--r--LICENSE.LGPL502
-rw-r--r--README.md72
-rw-r--r--RELEASE_NOTES2911
-rwxr-xr-xTOOLS/appveyor-build.sh9
-rwxr-xr-xTOOLS/appveyor-install.sh61
-rw-r--r--TOOLS/lua/audio-hotplug-test.lua5
-rw-r--r--TOOLS/lua/autoload.lua2
-rwxr-xr-xTOOLS/osxbundle.py6
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Info.plist2
-rwxr-xr-xTOOLS/osxbundle/mpv.app/Contents/MacOS/mpv-wrapper.sh13
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf1
-rwxr-xr-xTOOLS/travis-deps8
-rw-r--r--VERSION2
-rw-r--r--appveyor.yml4
-rw-r--r--audio/aconverter.c641
-rw-r--r--audio/aconverter.h39
-rw-r--r--audio/aframe.c30
-rw-r--r--audio/aframe.h3
-rw-r--r--audio/audio.c37
-rw-r--r--audio/audio.h3
-rw-r--r--audio/audio_buffer.c136
-rw-r--r--audio/audio_buffer.h12
-rw-r--r--audio/decode/dec_audio.c13
-rw-r--r--audio/filter/af.c12
-rw-r--r--audio/filter/af.h14
-rw-r--r--audio/filter/af_channels.c255
-rw-r--r--audio/filter/af_equalizer.c215
-rw-r--r--audio/filter/af_lavcac3enc.c2
-rw-r--r--audio/filter/af_lavfi.c18
-rw-r--r--audio/filter/af_lavrresample.c492
-rw-r--r--audio/filter/af_pan.c206
-rw-r--r--audio/filter/af_volume.c188
-rw-r--r--audio/format.c2
-rw-r--r--audio/out/ao.c60
-rw-r--r--audio/out/ao.h2
-rw-r--r--audio/out/ao_alsa.c41
-rw-r--r--audio/out/ao_jack.c4
-rw-r--r--audio/out/ao_oss.c4
-rw-r--r--audio/out/internal.h8
-rw-r--r--audio/out/pull.c4
-rw-r--r--audio/out/push.c92
-rw-r--r--common/av_common.c55
-rw-r--r--common/av_common.h3
-rw-r--r--common/av_log.c30
-rw-r--r--common/encode_lavc.c2
-rw-r--r--common/msg.c20
-rw-r--r--common/recorder.c2
-rw-r--r--demux/demux.c1702
-rw-r--r--demux/demux.h21
-rw-r--r--demux/demux_lavf.c38
-rw-r--r--demux/demux_mf.c8
-rw-r--r--demux/demux_mkv.c236
-rw-r--r--demux/demux_playlist.c20
-rw-r--r--demux/demux_timeline.c38
-rw-r--r--demux/ebml.c67
-rw-r--r--demux/ebml.h3
-rw-r--r--demux/packet.c15
-rw-r--r--demux/packet.h10
-rw-r--r--etc/builtin.conf7
-rw-r--r--etc/input.conf3
-rw-r--r--etc/mpv.conf4
-rw-r--r--etc/restore-old-bindings.conf7
-rw-r--r--input/cmd_list.c6
-rw-r--r--input/input.c22
-rw-r--r--input/ipc-unix.c24
-rw-r--r--libmpv/client.h2
-rw-r--r--libmpv/opengl_cb.h54
-rw-r--r--misc/json.c35
-rw-r--r--misc/json.h1
-rw-r--r--misc/node.c15
-rw-r--r--misc/node.h3
-rw-r--r--options/m_config.c47
-rw-r--r--options/m_option.c134
-rw-r--r--options/m_option.h11
-rw-r--r--options/m_property.h2
-rw-r--r--options/options.c102
-rw-r--r--options/options.h22
-rw-r--r--options/path.c12
-rw-r--r--osdep/android/posix-spawn.c72
-rw-r--r--osdep/android/posix-spawn.h43
-rw-r--r--osdep/atomic.h3
-rw-r--r--osdep/io.c329
-rw-r--r--osdep/io.h55
-rw-r--r--osdep/macosx_application.m34
-rw-r--r--osdep/path-macosx.m2
-rw-r--r--osdep/polldev.c75
-rw-r--r--osdep/polldev.h7
-rw-r--r--osdep/posix-spawn.h (renamed from video/out/win32/exclusive_hack.h)15
-rw-r--r--osdep/subprocess-posix.c2
-rw-r--r--osdep/subprocess-win.c2
-rw-r--r--osdep/terminal-unix.c64
-rw-r--r--osdep/timer-linux.c9
-rw-r--r--player/audio.c514
-rw-r--r--player/client.c2
-rw-r--r--player/command.c200
-rw-r--r--player/command.h3
-rw-r--r--player/core.h9
-rw-r--r--player/external_files.c33
-rw-r--r--player/javascript.c40
-rw-r--r--player/javascript/defaults.js2
-rw-r--r--player/lavfi.c4
-rw-r--r--player/loadfile.c75
-rw-r--r--player/lua.c50
-rw-r--r--player/lua/defaults.lua1
-rw-r--r--player/lua/osc.lua55
-rw-r--r--player/lua/stats.lua735
-rw-r--r--player/lua/ytdl_hook.lua13
-rw-r--r--player/main.c25
-rw-r--r--player/misc.c88
-rw-r--r--player/osd.c49
-rw-r--r--player/playloop.c72
-rw-r--r--player/screenshot.c11
-rw-r--r--player/scripting.c16
-rw-r--r--player/video.c42
-rw-r--r--stream/audio_in.h4
-rw-r--r--stream/cache.c42
-rw-r--r--stream/dvb_tune.c175
-rw-r--r--stream/dvb_tune.h3
-rw-r--r--stream/dvbin.h15
-rw-r--r--stream/frequencies.h5
-rw-r--r--stream/stream.c4
-rw-r--r--stream/stream_cdda.c5
-rw-r--r--stream/stream_dvb.c229
-rw-r--r--stream/stream_dvd_common.h5
-rw-r--r--stream/stream_dvdnav.c4
-rw-r--r--stream/stream_libarchive.c86
-rw-r--r--stream/stream_libarchive.h9
-rw-r--r--stream/stream_smb.c5
-rw-r--r--stream/tv.h5
-rw-r--r--sub/ass_mp.c4
-rw-r--r--sub/dec_sub.c2
-rw-r--r--sub/lavc_conv.c7
-rw-r--r--sub/osd_libass.c7
-rw-r--r--sub/sd_ass.c6
-rw-r--r--test/gl_video.c2
-rw-r--r--video/csputils.c7
-rw-r--r--video/csputils.h1
-rw-r--r--video/d3d.c287
-rw-r--r--video/d3d.h42
-rw-r--r--video/decode/d3d.c461
-rw-r--r--video/decode/d3d.h83
-rw-r--r--video/decode/dec_video.c30
-rw-r--r--video/decode/dec_video.h16
-rw-r--r--video/decode/hw_cuda.c111
-rw-r--r--video/decode/hw_d3d11va.c684
-rw-r--r--video/decode/hw_dxva2.c724
-rw-r--r--video/decode/hw_videotoolbox.c246
-rw-r--r--video/decode/lavc.h151
-rw-r--r--video/decode/vd_lavc.c1093
-rw-r--r--video/filter/vf.c36
-rw-r--r--video/filter/vf_buffer.c85
-rw-r--r--video/filter/vf_convert.c133
-rw-r--r--video/filter/vf_crop.c128
-rw-r--r--video/filter/vf_d3d11vpp.c23
-rw-r--r--video/filter/vf_dsize.c113
-rw-r--r--video/filter/vf_expand.c173
-rw-r--r--video/filter/vf_flip.c55
-rw-r--r--video/filter/vf_format.c14
-rw-r--r--video/filter/vf_gradfun.c103
-rw-r--r--video/filter/vf_lavfi.c94
-rw-r--r--video/filter/vf_lavfi.h25
-rw-r--r--video/filter/vf_mirror.c33
-rw-r--r--video/filter/vf_noformat.c67
-rw-r--r--video/filter/vf_pullup.c78
-rw-r--r--video/filter/vf_rotate.c101
-rw-r--r--video/filter/vf_scale.c253
-rw-r--r--video/filter/vf_stereo3d.c220
-rw-r--r--video/filter/vf_sub.c15
-rw-r--r--video/filter/vf_vapoursynth.c6
-rw-r--r--video/filter/vf_vavpp.c140
-rw-r--r--video/filter/vf_vdpaupp.c15
-rw-r--r--video/filter/vf_yadif.c92
-rw-r--r--video/fmt-conversion.c12
-rw-r--r--video/gpu_memcpy.c135
-rw-r--r--video/gpu_memcpy.h8
-rw-r--r--video/hwdec.c89
-rw-r--r--video/hwdec.h123
-rw-r--r--video/img_format.c16
-rw-r--r--video/img_format.h2
-rw-r--r--video/mp_image.c223
-rw-r--r--video/mp_image.h27
-rw-r--r--video/mp_image_pool.c41
-rw-r--r--video/mp_image_pool.h2
-rw-r--r--video/out/cocoa/window.m5
-rw-r--r--video/out/d3d11/context.c244
-rw-r--r--video/out/d3d11/hwdec_d3d11va.c249
-rw-r--r--video/out/d3d11/ra_d3d11.c2371
-rw-r--r--video/out/d3d11/ra_d3d11.h35
-rw-r--r--video/out/drm_atomic.c245
-rw-r--r--video/out/drm_atomic.h55
-rw-r--r--video/out/drm_common.c35
-rw-r--r--video/out/drm_common.h10
-rw-r--r--video/out/drm_prime.c85
-rw-r--r--video/out/drm_prime.h (renamed from video/out/wayland/memfile.h)23
-rw-r--r--video/out/gpu/context.c223
-rw-r--r--video/out/gpu/context.h101
-rw-r--r--video/out/gpu/d3d11_helpers.c (renamed from video/out/opengl/d3d11_helpers.c)115
-rw-r--r--video/out/gpu/d3d11_helpers.h (renamed from video/out/opengl/d3d11_helpers.h)10
-rw-r--r--video/out/gpu/hwdec.c (renamed from video/out/opengl/hwdec.c)101
-rw-r--r--video/out/gpu/hwdec.h (renamed from video/out/opengl/hwdec.h)21
-rw-r--r--video/out/gpu/lcms.c (renamed from video/out/opengl/lcms.c)2
-rw-r--r--video/out/gpu/lcms.h (renamed from video/out/opengl/lcms.h)0
-rw-r--r--video/out/gpu/osd.c (renamed from video/out/opengl/osd.c)20
-rw-r--r--video/out/gpu/osd.h (renamed from video/out/opengl/osd.h)2
-rw-r--r--video/out/gpu/ra.c (renamed from video/out/opengl/ra.c)61
-rw-r--r--video/out/gpu/ra.h (renamed from video/out/opengl/ra.h)47
-rw-r--r--video/out/gpu/shader_cache.c (renamed from video/out/opengl/shader_cache.c)254
-rw-r--r--video/out/gpu/shader_cache.h (renamed from video/out/opengl/shader_cache.h)12
-rw-r--r--video/out/gpu/spirv.c78
-rw-r--r--video/out/gpu/spirv.h41
-rw-r--r--video/out/gpu/spirv_shaderc.c125
-rw-r--r--video/out/gpu/user_shaders.c (renamed from video/out/opengl/user_shaders.c)2
-rw-r--r--video/out/gpu/user_shaders.h (renamed from video/out/opengl/user_shaders.h)4
-rw-r--r--video/out/gpu/utils.c332
-rw-r--r--video/out/gpu/utils.h105
-rw-r--r--video/out/gpu/video.c (renamed from video/out/opengl/video.c)1048
-rw-r--r--video/out/gpu/video.h (renamed from video/out/opengl/video.h)17
-rw-r--r--video/out/gpu/video_shaders.c (renamed from video/out/opengl/video_shaders.c)74
-rw-r--r--video/out/gpu/video_shaders.h (renamed from video/out/opengl/video_shaders.h)2
-rw-r--r--video/out/opengl/common.c24
-rw-r--r--video/out/opengl/common.h4
-rw-r--r--video/out/opengl/context.c449
-rw-r--r--video/out/opengl/context.h152
-rw-r--r--video/out/opengl/context_android.c152
-rw-r--r--video/out/opengl/context_angle.c335
-rw-r--r--video/out/opengl/context_cocoa.c71
-rw-r--r--video/out/opengl/context_drm_egl.c354
-rw-r--r--video/out/opengl/context_dxinterop.c187
-rw-r--r--video/out/opengl/context_glx.c (renamed from video/out/opengl/context_x11.c)196
-rw-r--r--video/out/opengl/context_mali_fbdev.c58
-rw-r--r--video/out/opengl/context_rpi.c84
-rw-r--r--video/out/opengl/context_vdpau.c202
-rw-r--r--video/out/opengl/context_wayland.c225
-rw-r--r--video/out/opengl/context_win.c (renamed from video/out/opengl/context_w32.c)244
-rw-r--r--video/out/opengl/context_x11egl.c84
-rw-r--r--video/out/opengl/egl_helpers.c114
-rw-r--r--video/out/opengl/egl_helpers.h19
-rw-r--r--video/out/opengl/formats.h1
-rw-r--r--video/out/opengl/gl_utils.c291
-rw-r--r--video/out/opengl/gl_utils.h56
-rw-r--r--video/out/opengl/hwdec_cuda.c13
-rw-r--r--video/out/opengl/hwdec_d3d11egl.c11
-rw-r--r--video/out/opengl/hwdec_d3d11eglrgb.c10
-rw-r--r--video/out/opengl/hwdec_drmprime_drm.c268
-rw-r--r--video/out/opengl/hwdec_dxva2egl.c11
-rw-r--r--video/out/opengl/hwdec_dxva2gldx.c11
-rw-r--r--video/out/opengl/hwdec_ios.m21
-rw-r--r--video/out/opengl/hwdec_osx.c13
-rw-r--r--video/out/opengl/hwdec_rpi.c3
-rw-r--r--video/out/opengl/hwdec_vaegl.c87
-rw-r--r--video/out/opengl/hwdec_vaglx.c226
-rw-r--r--video/out/opengl/hwdec_vdpau.c3
-rw-r--r--video/out/opengl/ra_gl.c34
-rw-r--r--video/out/opengl/ra_gl.h3
-rw-r--r--video/out/opengl/utils.c533
-rw-r--r--video/out/opengl/utils.h151
-rw-r--r--video/out/vo.c80
-rw-r--r--video/out/vo.h7
-rw-r--r--video/out/vo_caca.c5
-rw-r--r--video/out/vo_direct3d.c5
-rw-r--r--video/out/vo_drm.c4
-rw-r--r--video/out/vo_gpu.c336
-rw-r--r--video/out/vo_lavc.c18
-rw-r--r--video/out/vo_mediacodec_embed.c119
-rw-r--r--video/out/vo_opengl.c470
-rw-r--r--video/out/vo_opengl_cb.c64
-rw-r--r--video/out/vo_rpi.c4
-rw-r--r--video/out/vo_vaapi.c314
-rw-r--r--video/out/vo_wayland.c682
-rw-r--r--video/out/vo_x11.c41
-rw-r--r--video/out/vo_xv.c24
-rw-r--r--video/out/vulkan/common.h58
-rw-r--r--video/out/vulkan/context.c518
-rw-r--r--video/out/vulkan/context.h13
-rw-r--r--video/out/vulkan/context_wayland.c133
-rw-r--r--video/out/vulkan/context_win.c105
-rw-r--r--video/out/vulkan/context_xlib.c117
-rw-r--r--video/out/vulkan/formats.c55
-rw-r--r--video/out/vulkan/formats.h16
-rw-r--r--video/out/vulkan/malloc.c423
-rw-r--r--video/out/vulkan/malloc.h35
-rw-r--r--video/out/vulkan/ra_vk.c1747
-rw-r--r--video/out/vulkan/ra_vk.h31
-rw-r--r--video/out/vulkan/spirv_nvidia.c54
-rw-r--r--video/out/vulkan/utils.c729
-rw-r--r--video/out/vulkan/utils.h154
-rw-r--r--video/out/w32_common.c451
-rw-r--r--video/out/wayland/buffer.c140
-rw-r--r--video/out/wayland/buffer.h102
-rw-r--r--video/out/wayland/memfile.c105
-rw-r--r--video/out/wayland/server-decoration.xml94
-rw-r--r--video/out/wayland_common.c1785
-rw-r--r--video/out/wayland_common.h182
-rw-r--r--video/out/win32/exclusive_hack.c97
-rw-r--r--video/out/x11_common.h5
-rw-r--r--video/sws_utils.c16
-rw-r--r--video/vaapi.c586
-rw-r--r--video/vaapi.h48
-rw-r--r--video/vdpau.c234
-rw-r--r--video/vdpau.h15
-rw-r--r--video/vt.c74
-rw-r--r--video/vt.h16
-rw-r--r--waftools/checks/custom.py11
-rw-r--r--waftools/checks/generic.py8
-rw-r--r--waftools/dependencies.py64
-rw-r--r--waftools/deps_parser.py211
-rw-r--r--waftools/detections/compiler.py2
-rw-r--r--waftools/fragments/cuda.c3
-rw-r--r--waftools/generators/headers.py1
-rw-r--r--waftools/generators/sources.py26
-rw-r--r--waftools/syms.py2
-rw-r--r--wscript407
-rw-r--r--wscript_build.py137
331 files changed, 23547 insertions, 19336 deletions
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
index 028fd8e..533f943 100644
--- a/.github/ISSUE_TEMPLATE
+++ b/.github/ISSUE_TEMPLATE
@@ -22,4 +22,5 @@ or ignored.
### Sample files
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
-or similar sites. (Only needed if the issue cannot be reproduced without it.) \ No newline at end of file
+or similar sites. (Only needed if the issue cannot be reproduced without it.)
+Do not use garbage like "cloud storage", especially not Google Drive. \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 4f8e976..162941e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,9 +5,8 @@ os:
#- osx
env:
matrix:
- - LIBAV=libav-git
- - LIBAV=ffmpeg-stable
- LIBAV=ffmpeg-git
+ - LIBAV=libav-git
global:
# Coverity token
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
@@ -32,8 +31,6 @@ matrix:
- os: osx
compiler: gcc
- os: linux
- env: LIBAV=ffmpeg-stable
- - os: linux
compiler: clang
before_install: TOOLS/travis-deps libass-stable $LIBAV
diff --git a/Copyright b/Copyright
index cf02507..4a8b056 100644
--- a/Copyright
+++ b/Copyright
@@ -1,351 +1,95 @@
mpv is a fork of mplayer2, which is a fork of MPlayer.
-mpv as a whole is licensed as GPL version 2 or later (see LICENSE). Most source
-files are GPLv2+, but some files are available under a more liberal license,
-such as LGPLv2.1+, BSD, MIT, ISC, and possibly others. Look at the copyright
-header of each source file, and grep the sources for "Copyright" if you need
-to know details. C source files without Copyright notice are licensed as
-LGPLv2.1+. Also see the list of files with specific licenses below (not all
-files can have a standard license header).
+mpv as a whole is licensed under the GNU General Public License GPL version 2
+or later (called GPLv2+ in this document, see LICENSE.GPL for full license
+text) by default, or the GNU Lesser General Public License LGPL version 2 or
+later (LGPLv2.1+ in this document, see LICENSE.LGPL for full license text) if
+built with the --enable-lgpl configure switch.
-All new contributions must be LGPLv2.1+ licensed, or if the changes are done on
-GPL code, must come with the implicit agreement that the project can relicense
-the code to LGPLv2.1+ at a later point without asking the contributor. (This
-is a safeguard for making potential relicensing of the project to LGPLv2.1+
-easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
+Most source files are LGPLv2.1+ or GPLv2+, but some files are available under
+more liberal licenses, such as BSD, MIT, ISC, and possibly others. Look at the
+copyright header of each source file, and grep the sources for "Copyright" if
+you need to know details. C source files without Copyright notice are usually
+licensed as LGPLv2.1+. Also see the list of files with specific licenses below
+(not all files can have a standard license header).
-For information about authors and contributors, consult the git log, which
-contains the complete SVN and CVS history as well.
+All new contributions must be LGPLv2.1+ licensed. Using a more liberal license
+compatible to LGPLv2.1+ is also ok.
-Note that mplayer2 as a whole is licensed under GPLv3+. This is because it uses
-a copy of talloc (part of Samba), which is LGPLv3+, and the next compatible
-license for this mix is GPLv3+.
+If changes are done on GPL code, must come with the implicit agreement that the
+project can relicense the changes to LGPLv2.1+ at a later point without asking
+the contributor. This is a safeguard for making potential relicensing of
+remaining GPL code to LGPLv2.1+ easier.
-MPlayer as a whole is licensed under GPLv2 (incompatible to GPLv3!), because
-some files are licensed to GPLv2 (and _not_ any later version of the license).
-In particular, this affects the file libmpdemux/demux_ty_osd.c. It is disabled
-under mplayer2, and has been removed from mpv.
+For information about authors and contributors, consult the git log, which
+contains the complete SVN and CVS history as well.
"v2.1+" in this context means "version 2.1 or later".
Some libraries are GPLv2+ or GPLv3+ only. Building mpv with Samba support makes
it GPLv3+.
-Source files with specific licenses:
-- etc/input.conf is LGPLv2.1+
-- etc/builtin.conf is LGPLv2.1+
-- etc/encoding-profiles.conf is LGPLv2.1+
-- all mpv icons and derived files are LGPLv2.1+ (mpv-icon-8bit-16x16.png etc.)
-- everything else under etc/ is unknown
-- everything under DOCS/man/ is GPLv2+
-- sub/osd_font.otf is LGPLv2.1+
-- version.sh is LGPLv2+
-- bootstrap.py is unknown (probably GPLv2+ or LGPLv2+)
-- the build system (wscript, waftools/*) is LGPLv2+, some parts BSD
-
-Some files are marked with "Almost LGPL." These files are GPL, but all authors
-have agreed to relicense them to LGPL. the problem is that one of the authors is
-Michael Niedermayer, who cited the following conditions for relicensing:
-
- http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2016-September/073535.html
+mpv can be built as LGPLv2.1+ with the --enable-lgpl configure option. To add
+a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
+by asking the MPlayer authors for permission. Since permission could not be
+obtained from everyone, LGPL mode disables the following features, some of
+them quite central:
+- no audio filtering, which breaks: --af, pitch correction, fine control over
+ downmix/upmix/resampling behavior
+- Linux X11 video output
+- BSD audio output via OSS
+- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
+- Linux TV input
+- minor features: jack, DVD, CDDA, SMB, CACA, legacy direct3d VO
+Some of these will be fixed in the future. The intended use for LGPL mode is
+with libmpv, and currently it's not recommended to build mpv CLI in LGPL mode
+at all.
-We assume the first 3 conditions are fulfilled. The last condition can be
-interpreted as that his code can be changed to LGPL only as soon as the "core"
-of mpv changes to LGPL. We interpret "core" as something minimal, that can
-actually be built and run, with all GPL code disabled.
+The following files are still GPL only (--enable-lgpl disables them):
-Some files are LGPLv3+. This is due to the contributions of a single developer
-going by the SVN username "iive". The chosen license of this project is
-LGPLv2.1+. The affected files will be changed to LGPLv2.1+ at the earliest
-opportunity, for example if his contributions disappear by being replaced
-or removed. All new contributions to these files are implied to be LGPLv2.1+.
-
-LGPL relicensing status. Files which require some sort of action to get a basic
-LGPL player core (relicensing, replacement, or partial/full rewrites) are marked
-with an "x".
-
- audio/decode/ad.h LGPL
- audio/decode/ad_lavc.c LGPL
- audio/decode/ad_spdif.c LGPL
- audio/decode/dec_audio.* LGPL
-x audio/filter/af.* must be killed (main author disagreed)
- (required for LGPL core: a new filter chain must be written)
- audio/filter/af_channel.c must be killed (main author disagreed)
- audio/filter/af_equalizer.c must be killed (main author disagreed)
- audio/filter/af_pan.c must be killed (main author disagreed)
- audio/filter/af_volume.c must be killed (main author disagreed)
- audio/filter/equalizer.h must be killed (main author disagreed)
- audio/filter/tools.c must be killed (main author disagreed)
- audio/filter/af_format.c LGPL
- audio/filter/af_lavc3enc.c LGPL
- audio/filter/af_lavfi.c LGPL
- audio/filter/af_scaletempo.c LGPL
- audio/filter/af_rubberband.c LGPL
- audio/out/ao.c LGPL
- audio/out/ao.h LGPL
- audio/out/ao_alsa.c extremely hard (original author did not decide)
- audio/out/ao_audiounit.m LGPL
- audio/out/ao_coreaudio.c LGPL
- audio/out/ao_coreaudio_chmap.c LGPL
- audio/out/ao_coreaudio_chmap.h LGPL
- audio/out/ao_coreaudio_exclusiv LGPL
- audio/out/ao_coreaudio_properti LGPL
- audio/out/ao_coreaudio_properti LGPL
- audio/out/ao_coreaudio_utils.c LGPL
- audio/out/ao_coreaudio_utils.h LGPL
+ audio/filter/* will be replaced with new filter chain
+ audio/filter/af_format.c mostly LGPL (except af glue code)
+ audio/filter/af_lavc3enc.c as above
+ audio/filter/af_lavfi.c as above
+ audio/filter/af_scaletempo.c as above
+ audio/filter/af_rubberband.c as above
audio/out/ao_jack.c will stay GPL
- audio/out/ao_lavc.c LGPL
- audio/out/ao_null.c LGPL
- audio/out/ao_openal.c LGPL
- audio/out/ao_opensles.c LGPL
audio/out/ao_oss.c will stay GPL
- audio/out/ao_pcm.c LGPL
- audio/out/ao_pulse.c LGPL
- audio/out/ao_rsound.c LGPL
- audio/out/ao_sdl.c LGPL
- audio/out/ao_sndio.c LGPL (BSD)
- audio/out/ao_wasapi.c LGPL
- audio/out/ao_wasapi_changenotif LGPL
- audio/out/ao_wasapi.h LGPL
- audio/out/ao_wasapi_utils.c LGPL
- audio/out/internal.h LGPL
- audio/out/pull.c LGPL
- audio/out/push.c LGPL
-x audio/audio.* very hard (mp_audio based of anders' af_audio)
- (required for LGPL core: determine if the struct can be used anyway, or
- devise a cheap AVFrame wrapper)
- audio/audio_buffer.* LGPL
- audio/chmap.* LGPL
- audio/chmap_sel.* LGPL
- audio/fmt-conversion.* LGPL
- audio/format.* LGPL
- common/av_common.* LGPL
- common/av_log.c almost LGPL
- common/av_log.h LGPL
- common/codecs.* LGPL
- common/common.* LGPL
- common/encode.h LGPL
- common/encode_lavc.* LGPL
- common/global.h LGPL
- common/msg.c almost LGPL
- common/msg_control.h LGPL
- common/msg.h LGPL
- common/playlist.* LGPL
- common/recorder.* LGPL
- common/tags.* LGPL
- common/version.c LGPL
- demux/codec_tags.* LGPL
- demux/cue.* LGPL
- demux/demux.* LGPL
- demux/demux_cue.c LGPL
- demux/demux_disc.c LGPL
- demux/demux_edl.c LGPL
- demux/demux_lavf.c almost LGPL
- demux/demux_libarchive.c LGPL
- demux/demux_mf.c LGPLv3+
- demux/demux_mkv.c LGPL (mostly)
- demux/demux_mkv_timeline.c LGPL
- demux/demux_null.c LGPL
- demux/demux_playlist.c LGPL
- demux/demux_rar.c LGPL
- demux/demux_raw.c almost LGPL
- demux/demux_timeline.c LGPL
+ audio/audio.* needed by af code only
demux/demux_tv.c will stay GPL
- demux/ebml.* LGPL
- demux/matroska.h LGPL
- demux/packet.* LGPL
- demux/stheader.h LGPL
- demux/timeline.* LGPL
- input/cmd_* LGPL
- input/event.* LGPL
- input/input.* LGPL
- input/ipc.c LGPL
- input/ipc-unix.c LGPL
- input/ipc-win.c LGPL
- input/keycodes.* LGPL
- input/pipe-win32.c LGPL
- libmpv/*.* LGPL
- misc/*.* LGPL
- options/m_config.* LGPL
- options/m_option.c almost LGPL
- options/m_option.h LGPL
- options/m_property.* LGPL
- options/options.* LGPL
- options/parse_commandline.* LGPL
- options/parse_configfile.* LGPL
- options/path.* LGPL
- osdep/android/* LGPL (BSD)
- osdep/ar/* LGPL (BSD)
- osdep/atomic.h LGPL
- osdep/compiler.h LGPL
- osdep/endian.h LGPL
- osdep/glob-win.c LGPL
- osdep/io.* LGPL
- osdep/macosx_application.h LGPL
- osdep/macosx_application.m LGPL
- osdep/macosx_application_objc.h LGPL
- osdep/macosx_compat.h LGPL
- osdep/macosx_events.* LGPL
- osdep/macosx_events_objc.h LGPL
- osdep/macosx_touchbar.* LGPL
- osdep/macosx_versions.h LGPL
- osdep/main-fn-cocoa.c LGPL
- osdep/main-fn.h LGPL
- osdep/main-fn-unix.c LGPL
- osdep/main-fn-win.c LGPL
- osdep/mpv.exe.manifest LGPL
- osdep/mpv.rc LGPL
- osdep/path.h LGPL
- osdep/path-macosx.m LGPL
- osdep/path-unix.c LGPL
- osdep/path-win.c LGPL
- osdep/semaphore.h LGPL
- osdep/semaphore_osx.c LGPL
- osdep/strnlen.h LGPL
- osdep/subprocess.* LGPL
- osdep/subprocess-posix.c LGPL
- osdep/subprocess-win.c LGPL
- osdep/terminal.h LGPL
- osdep/terminal-unix.c LGPL
- osdep/terminal-win.c LGPL
- osdep/threads.* LGPL
- osdep/timer.c LGPL
- osdep/timer.h LGPL
- osdep/timer-darwin.c LGPL (MIT)
- osdep/timer-linux.c LGPL
- osdep/timer-win2.c LGPL
- osdep/w32_keyboard.c LGPL
- osdep/w32_keyboard.h LGPL
- osdep/win32-console-wrapper.c LGPL (BSD)
- osdep/win32/* LGPL (ISC)
- osdep/windows_utils.* LGPL
-x player/audio.c LGPL (dysfunctional due to libaf)
- player/client.* LGPL (ISC)
- player/command.c LGPL
- player/command.h LGPL
- player/configfiles.c LGPL
- player/core.h LGPL
- player/external_files.* LGPL
- player/lavfi.* LGPL
- player/loadfile.c LGPL
- player/lua/*.* LGPL
- player/lua.c LGPL
- player/misc.c LGPL
- player/osd.c LGPL
- player/playloop.c LGPL
- player/screenshot.* LGPL
- player/scripting.* LGPL
- player/sub.c LGPL
- player/video.c LGPL
stream/ai_* will stay GPL (TV code)
stream/audio_in.* will stay GPL (TV code)
- stream/cache.c LGPLv3+
- stream/cache_file.c LGPL
- stream/cookies.* LGPL
stream/dvb* must stay GPL
stream/frequencies.* must stay GPL
- stream/rar.* LGPL
- stream/stream_avdevice.c LGPL
- stream/stream_bluray.c LGPL
- stream/stream.* LGPL
- stream/stream_cb.c LGPL
stream/stream_cdda.c unknown
stream/stream_dvb.* must stay GPL
stream/stream_dvd.c unknown
stream/stream_dvd_common.* unknown
stream/stream_dvdnav.c unknown
- stream/stream_edl.c LGPL
- stream/stream_file.c LGPL
- stream/stream_lavf.c LGPL
- stream/stream_libarchive.* LGPL
- stream/stream_memory.c LGPL
- stream/stream_mf.c LGPL
- stream/stream_null.c LGPL
- stream/stream_rar.c LGPL
stream/stream_smb.c will stay GPLv3
stream/stream_tv.c will stay GPL
stream/tv* will stay GPL
- sub/* LGPL
- ta/* LGPL (ISC)
- video/decode/d3d.* LGPL
- video/decode/dec_video.* almost LGPL
- video/decode/hw_cuda.c LGPL
- video/decode/hw_d3d11va.c LGPL
- video/decode/hw_dxva2.c LGPL
- video/decode/hw_videotoolbox.c LGPL
- video/decode/lavc.h almost LGPLv3+
- video/decode/vd.h LGPL
- video/decode/vd_lavc.c almost LGPLv3+
- video/filter/refqueue.* LGPL
- video/filter/vf.c LGPL
- video/filter/vf.h LGPL (mostly)
- video/filter/vf_buffer.c LGPL
- video/filter/vf_crop.c will be deleted
- video/filter/vf_d3d11vpp.c LGPL
- video/filter/vf_dsize.c will be deleted
- video/filter/vf_expand.c will be deleted
- video/filter/vf_flip.c will be deleted
- video/filter/vf_format.c will be deleted
- video/filter/vf_gradfun.c will be deleted
- video/filter/vf_lavfi.* LGPL
- video/filter/vf_mirror.c will be deleted
- video/filter/vf_noformat.c will be deleted
- video/filter/vf_pullup.c will be deleted
- video/filter/vf_rotate.c will be deleted
- video/filter/vf_scale.c will be deleted
- video/filter/vf_stereo3d.c will be deleted
- video/filter/vf_sub.c will be deleted
- video/filter/vf_vapoursynth.c LGPL
- video/filter/vf_vavpp.c LGPL
- video/filter/vf_vdpaupp.c LGPL
- video/filter/vf_yadif.c will be deleted
- video/csputils.* LGPL
- video/fmt-conversion.* LGPL
- video/gpu_memcpy.* will be deleted
- video/hwdec.* LGPL
- video/image_writer.* LGPL
- video/img_format.* LGPL
- video/mp_image.* almost LGPL
- video/mp_image_pool.* LGPL
- video/out/aspect.* LGPL
- video/out/bitmap_packer.* LGPL
- video/out/cocoa* LGPL
- video/out/d3d_shader_420p.h LGPL
- video/out/d3d_shader_nv12.h LGPL
- video/out/d3d_shader_yuv.hlsl LGPL
- video/out/dither.* LGPL
- video/out/drm_common.* LGPL
- video/out/filter_kernels.* LGPL (BSD)
- video/out/opengl/hwdec_vaglx.c GPL
- video/out/opengl/* LGPL
- video/out/vo.c LGPL
- video/out/vo.h LGPL
video/out/vo_caca.c unknown
video/out/vo_direct3d.c unknown
- video/out/vo_drm.c LGPL
- video/out/vo_image.c LGPL
- video/out/vo_lavc.c LGPL
- video/out/vo_null.c LGPL
- video/out/vo_opengl.c LGPL
- video/out/vo_opengl_cb.c LGPL
- video/out/vo_rpi.c LGPL
- video/out/vo_sdl.c LGPL
- video/out/vo_tct.c LGPL
video/out/vo_vaapi.c probably impossible (some company's code)
video/out/vo_vdpau.c probably impossible (nVidia's code)
- video/out/vo_wayland.c LGPL
video/out/vo_x11.c probably impossible
video/out/vo_xv.c probably impossible
- video/out/w32_common.* LGPL
- video/out/wayland* LGPL
- video/out/win32/* LGPL
- video/out/win_state.* LGPL
video/out/x11_common.* probably impossible
- video/out/x11_icon.bin LGPL
- video/sws_utils.* almost LGPL
- video/vaapi.* hard (GPL-only parts must be ifdefed)
video/vdpau.c hard (GPL-only parts must be ifdefed)
- video/vdpau_functions.inc LGPL
video/vdpau.h unknown
video/vdpau_mixer.* actual code must be rewritten
- video/vt.* LGPL
+ DOCS/man/ GPLv2+
+ bootstrap.py unknown license, probably GPLv2+ or LGPLv2+
+ etc/mplayer-input.conf unknown license, probably GPLv2+
+ mpv.desktop unknown license, probably GPLv2+
+ etc/restore-old-bindings.conf unknown license, probably GPLv2+
+
+The following files contain some optional GPL code (--enable-lgpl disables it):
+
+ options/parse_commandline.c dvd:// expansion
+ player/audio.c libaf glue code
+
+None of the exceptions listed above affect the final binary if it's built as
+LGPL. Linked libraries still can affect the final license (for example if
+FFmpeg was built as GPL).
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst
index 5fd8d84..7dcf031 100644
--- a/DOCS/client-api-changes.rst
+++ b/DOCS/client-api-changes.rst
@@ -32,6 +32,12 @@ API changes
::
+ --- mpv 0.29.0 ---
+ 1.26 - remove glMPGetNativeDisplay("drm") support
+ - add mpv_opengl_cb_window_pos and mpv_opengl_cb_drm_params and
+ support via glMPGetNativeDisplay() for using it
+ - make --stop-playback-on-init-failure=no the default in libmpv (just
+ like in mpv CLI)
--- mpv 0.27.0 ---
1.25 - remove setting "no-" options via mpv_set_option*(). (See corresponding
deprecation in 0.23.0.)
diff --git a/DOCS/compile-windows.md b/DOCS/compile-windows.md
index 48895f5..08d4564 100644
--- a/DOCS/compile-windows.md
+++ b/DOCS/compile-windows.md
@@ -101,11 +101,14 @@ Installing MSYS2
1. Download an installer from https://msys2.github.io/
- It doesn't matter whether the i686 or the x86_64 version is used. Both can
- build 32-bit and 64-bit binaries when running on a 64-bit version of Windows.
+ Both the i686 and the x86_64 version of MSYS2 can build 32-bit and 64-bit
+ mpv binaries when running on a 64-bit version of Windows, but the x86_64
+ version is preferred since the larger address space makes it less prone to
+ fork() errors.
-2. Start a MinGW-w64 shell (``mingw64.exe``). Note that this is different
- from the MSYS2 shell that is started from the final installation dialog.
+2. Start a MinGW-w64 shell (``mingw64.exe``). **Note:** This is different from
+ the MSYS2 shell that is started from the final installation dialog. You must
+ close that shell and open a new one.
For a 32-bit build, use ``mingw32.exe``.
@@ -129,42 +132,37 @@ Installing mpv dependencies
```bash
# Install MSYS2 build dependencies and a MinGW-w64 compiler
-pacman -S git mingw-w64-x86_64-pkg-config python mingw-w64-x86_64-gcc
+pacman -S git python $MINGW_PACKAGE_PREFIX-{pkg-config,gcc}
-# Install the most important MinGW-w64 dependencies. libass, libbluray and
-# lcms2 are also pulled in as dependencies of ffmpeg.
-pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua51
-
-# Install additional (optional) dependencies
-pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess mingw-w64-x86_64-angleproject-git
+# Install the most important MinGW-w64 dependencies. libass and lcms2 are also
+# pulled in as dependencies of ffmpeg.
+pacman -S $MINGW_PACKAGE_PREFIX-{ffmpeg,libjpeg-turbo,lua51,angleproject-git}
```
-For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
-
Building mpv
------------
-Clone the latest mpv from git and install waf:
+Clone the latest mpv from git and install waf. **Note:** ``/usr/bin/python3``
+is invoked directly here, since an MSYS2 version of Python is required.
```bash
git clone https://github.com/mpv-player/mpv.git && cd mpv
-./bootstrap.py
+/usr/bin/python3 bootstrap.py
```
Finally, compile and install mpv. Binaries will be installed to
-``/mingw64/bin``.
+``/mingw64/bin`` or ``/mingw32/bin``.
```bash
-# For a 32-bit build, use --prefix=/mingw32 instead
-./waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=/mingw64
-./waf install
+/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=$MSYSTEM_PREFIX
+/usr/bin/python3 waf install
```
Or, compile and install both libmpv and mpv:
```bash
-./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
-./waf install
+/usr/bin/python3 waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=$MSYSTEM_PREFIX
+/usr/bin/python3 waf install
```
Linking libmpv with MSVC programs
diff --git a/DOCS/contribute.md b/DOCS/contribute.md
index f509a9a..a11b298 100644
--- a/DOCS/contribute.md
+++ b/DOCS/contribute.md
@@ -68,7 +68,7 @@ Sending patches
be documented in input.rst. All changes to the user interface (options,
properties, commands) must be documented with a small note in
interface-changes.rst (although documenting additions is optional, and
- obscure corner cases and potentially be skipped too). Changes to the libmpv
+ obscure corner cases can potentially be skipped too). Changes to the libmpv
API must be reflected in the libmpv's headers doxygen, and should be
documented in client-api-changes.rst.
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index d8c4900..c104e8a 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -19,6 +19,51 @@ Interface changes
::
+ --- mpv 0.28.0 ---
+ - rename --hwdec=mediacodec option to mediacodec-copy, to reflect
+ conventions followed by other hardware video decoding APIs
+ - drop previously deprecated --heartbeat-cmd and --heartbeat--interval
+ options
+ - rename --vo=opengl to --vo=gpu
+ - rename --opengl-backend to --gpu-context
+ - rename --opengl-shaders to --glsl-shaders
+ - rename --opengl-shader-cache-dir to --gpu-shader-cache-dir
+ - rename --opengl-tex-pad-x/y to --gpu-tex-pad-x/y
+ - rename --opengl-fbo-format to --fbo-format
+ - rename --opengl-gamma to --gamma-factor
+ - rename --opengl-debug to --gpu-debug
+ - rename --opengl-sw to --gpu-sw
+ - rename --opengl-vsync-fences to --swapchain-depth, and the interpretation
+ slightly changed. Now defaults to 3.
+ - rename the built-in profile `opengl-hq` to `gpu-hq`
+ - the semantics of --opengl-es=yes are slightly changed -> now requires GLES
+ - remove the (deprecated) alias --gpu-context=drm-egl
+ - remove the (deprecated) --vo=opengl-hq
+ - remove --opengl-es=force2 (use --opengl-es=yes --opengl-restrict=300)
+ - the --msg-level option now affects --log-file
+ - drop "audio-out-detected-device" property - this was unavailable on all
+ audio output drivers for quite a while (coreaudio used to provide it)
+ - deprecate --videotoolbox-format (use --hwdec-image-format, which affects
+ most other hwaccels)
+ - remove deprecated --demuxer-max-packets
+ - remove most of the deprecated audio and video filters
+ - remove the deprecated --balance option/property
+ - rename the --opengl-hwdec-interop option to --gpu-hwdec-interop, and
+ change some of its semantics: extend it take the strings "auto" and
+ "all". "all" loads all backends. "auto" behaves like "all" for
+ vo_opengl_cb, while on vo_gpu it loads nothing, but allows on demand
+ loading by the decoder. The empty string as option value behaves like
+ "auto". Old --hwdec values do not work anymore.
+ This option is hereby declared as unstable and may change any time - its
+ old use is deprecated, and it has very little use outside of debugging
+ now.
+ - change the --hwdec option from a choice to a plain string (affects
+ introspection of the option/property), also affects some properties
+ - rename --hwdec=rpi to --hwdec=mmal, sane for the -copy variant (no
+ backwards compatibility)
+ - deprecate the --ff-aid, --ff-vid, -ff-sid options and properties (there is
+ no replacement, but you can manually query the track property and use the
+ "ff-index" field to find the mpv track ID to imitate this behavior)
--- mpv 0.27.0 ---
- drop previously deprecated --field-dominance option
- drop previously deprecated "osd" command
diff --git a/DOCS/man/af.rst b/DOCS/man/af.rst
index b56fc91..e043171 100644
--- a/DOCS/man/af.rst
+++ b/DOCS/man/af.rst
@@ -91,81 +91,6 @@ Available filters are:
Select the libavcodec encoder used. Currently, this should be an AC-3
encoder, and using another codec will fail horribly.
-``equalizer=g1:g2:g3:...:g10``
- 10 octave band graphic equalizer, implemented using 10 IIR band-pass
- filters. This means that it works regardless of what type of audio is
- being played back. The center frequencies for the 10 bands are:
-
- === ==========
- No. frequency
- === ==========
- 0 31.25 Hz
- 1 62.50 Hz
- 2 125.00 Hz
- 3 250.00 Hz
- 4 500.00 Hz
- 5 1.00 kHz
- 6 2.00 kHz
- 7 4.00 kHz
- 8 8.00 kHz
- 9 16.00 kHz
- === ==========
-
- If the sample rate of the sound being played is lower than the center
- frequency for a frequency band, then that band will be disabled. A known
- bug with this filter is that the characteristics for the uppermost band
- are not completely symmetric if the sample rate is close to the center
- frequency of that band. This problem can be worked around by upsampling
- the sound using a resampling filter before it reaches this filter.
-
- ``<g1>:<g2>:<g3>:...:<g10>``
- floating point numbers representing the gain in dB for each frequency
- band (-12-12)
-
- .. admonition:: Example
-
- ``mpv --af=equalizer=11:11:10:5:0:-12:0:5:12:12 media.avi``
- Would amplify the sound in the upper and lower frequency region
- while canceling it almost completely around 1 kHz.
-
-``channels=nch[:routes]``
- Can be used for adding, removing, routing and copying audio channels. If
- only ``<nch>`` is given, the default routing is used. It works as follows:
- If the number of output channels is greater than the number of input
- channels, empty channels are inserted (except when mixing from mono to
- stereo; then the mono channel is duplicated). If the number of output
- channels is less than the number of input channels, the exceeding
- channels are truncated.
-
- ``<nch>``
- number of output channels (1-8)
- ``<routes>``
- List of ``,`` separated routes, in the form ``from1-to1,from2-to2,...``.
- Each pair defines where to route each channel. There can be at most
- 8 routes. Without this argument, the default routing is used. Since
- ``,`` is also used to separate filters, you must quote this argument
- with ``[...]`` or similar.
-
- .. admonition:: Examples
-
- ``mpv --af=channels=4:[0-1,1-0,2-2,3-3] media.avi``
- Would change the number of channels to 4 and set up 4 routes that
- swap channel 0 and channel 1 and leave channel 2 and 3 intact.
- Observe that if media containing two channels were played back,
- channels 2 and 3 would contain silence but 0 and 1 would still be
- swapped.
-
- ``mpv --af=channels=6:[0-0,0-1,0-2,0-3] media.avi``
- Would change the number of channels to 6 and set up 4 routes that
- copy channel 0 to channels 0 to 3. Channel 4 and 5 will contain
- silence.
-
- .. note::
-
- You should probably not use this filter. If you want to change the
- output channel layout, try the ``format`` filter, which can make mpv
- automatically up- and downmix standard channel layouts.
-
``format=format:srate:channels:out-format:out-srate:out-channels``
Does not do any format conversion itself. Rather, it may cause the
filter system to insert necessary conversion filters before or after this
@@ -205,107 +130,6 @@ Available filters are:
used to do conversion itself, unlike this one which lets the filter system
handle the conversion.
-``volume[=<volumedb>[:...]]``
- Implements software volume control. Use this filter with caution since it
- can reduce the signal to noise ratio of the sound. In most cases it is
- best to use the *Master* volume control of your sound card or the volume
- knob on your amplifier.
-
- *WARNING*: This filter is deprecated. Use the top-level options like
- ``--volume`` and ``--replaygain...`` instead.
-
- *NOTE*: This filter is not reentrant and can therefore only be enabled
- once for every audio stream.
-
- ``<volumedb>``
- Sets the desired gain in dB for all channels in the stream from -200 dB
- to +60 dB, where -200 dB mutes the sound completely and +60 dB equals a
- gain of 1000 (default: 0).
- ``replaygain-track``
- Adjust volume gain according to the track-gain replaygain value stored
- in the file metadata.
- ``replaygain-album``
- Like replaygain-track, but using the album-gain value instead.
- ``replaygain-preamp``
- Pre-amplification gain in dB to apply to the selected replaygain gain
- (default: 0).
- ``replaygain-clip=yes|no``
- Prevent clipping caused by replaygain by automatically lowering the
- gain (default). Use ``replaygain-clip=no`` to disable this.
- ``replaygain-fallback``
- Gain in dB to apply if the file has no replay gain tags. This option
- is always applied if the replaygain logic is somehow inactive. If this
- is applied, no other replaygain options are applied.
- ``softclip``
- Turns soft clipping on. Soft-clipping can make the
- sound more smooth if very high volume levels are used. Enable this
- option if the dynamic range of the loudspeakers is very low.
-
- *WARNING*: This feature creates distortion and should be considered a
- last resort.
- ``s16``
- Force S16 sample format if set. Lower quality, but might be faster
- in some situations.
- ``detach``
- Remove the filter if the volume is not changed at audio filter config
- time. Useful with replaygain: if the current file has no replaygain
- tags, then the filter will be removed if this option is enabled.
- (If ``--softvol=yes`` is used and the player volume controls are used
- during playback, a different volume filter will be inserted.)
-
- .. admonition:: Example
-
- ``mpv --af=volume=10.1 media.avi``
- Would amplify the sound by 10.1 dB and hard-clip if the sound level
- is too high.
-
-``pan=n:[<matrix>]``
- Mixes channels arbitrarily. Basically a combination of the volume and the
- channels filter that can be used to down-mix many channels to only a few,
- e.g. stereo to mono, or vary the "width" of the center speaker in a
- surround sound system. This filter is hard to use, and will require some
- tinkering before the desired result is obtained. The number of options for
- this filter depends on the number of output channels. An example how to
- downmix a six-channel file to two channels with this filter can be found
- in the examples section near the end.
-
- ``<n>``
- Number of output channels (1-8).
- ``<matrix>``
- A list of values ``[L00,L01,L02,...,L10,L11,L12,...,Ln0,Ln1,Ln2,...]``,
- where each element ``Lij`` means how much of input channel i is mixed
- into output channel j (range 0-1). So in principle you first have n
- numbers saying what to do with the first input channel, then n numbers
- that act on the second input channel etc. If you do not specify any
- numbers for some input channels, 0 is assumed.
- Note that the values are separated by ``,``, which is already used
- by the option parser to separate filters. This is why you must quote
- the value list with ``[...]`` or similar.
-
- .. admonition:: Examples
-
- ``mpv --af=pan=1:[0.5,0.5] media.avi``
- Would downmix from stereo to mono.
-
- ``mpv --af=pan=3:[1,0,0.5,0,1,0.5] media.avi``
- Would give 3 channel output leaving channels 0 and 1 intact, and mix
- channels 0 and 1 into output channel 2 (which could be sent to a
- subwoofer for example).
-
- .. note::
-
- If you just want to force remixing to a certain output channel layout,
- it is easier to use the ``format`` filter. For example,
- ``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
- remixing audio to 5.1 and output it like this.
-
- This filter supports the following ``af-command`` commands:
-
- ``set-matrix``
- Set the ``<matrix>`` argument dynamically. This can be used to change
- the mixing matrix at runtime, without reinitializing the entire filter
- chain.
-
``scaletempo[=option1:option2:...]``
Scales audio tempo without altering pitch, optionally synced to playback
speed (default).
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 1bc033e..b67b490 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -913,8 +913,8 @@ Property list
``file-size``
Length in bytes of the source file/stream. (This is the same as
- ``${stream-end}``. For ordered chapters and such, the
- size of the currently played segment is returned.)
+ ``${stream-end}``. For segmented/multi-part files, this will return the
+ size of the main or manifest file, whatever it is.)
``estimated-frame-count``
Total number of frames in current file.
@@ -954,8 +954,7 @@ Property list
``stream-path``
Filename (full path) of the stream layer filename. (This is probably
- useless. It looks like this can be different from ``path`` only when
- using e.g. ordered chapters.)
+ useless and is almost never different from ``path``.)
``stream-pos``
Raw byte position in source stream. Technically, this returns the position
@@ -1259,6 +1258,58 @@ Property list
Returns ``yes`` if the demuxer is idle, which means the demuxer cache is
filled to the requested amount, and is currently not reading more data.
+``demuxer-cache-state``
+ Various undocumented or half-documented things.
+
+ Each entry in ``seekable-ranges`` represents a region in the demuxer cache
+ that can be seeked to. If there are multiple demuxers active, this only
+ returns information about the "main" demuxer, but might be changed in
+ future to return unified information about all demuxers. The ranges are in
+ arbitrary order. Often, ranges will overlap for a bit, before being joined.
+ In broken corner cases, ranges may overlap all over the place.
+
+ The end of a seek range is usually smaller than the value returned by the
+ ``demuxer-cache-time`` property, because that property returns the guessed
+ buffering amount, while the seek ranges represent the buffered data that
+ can actually be used for cached seeking.
+
+ ``fw-bytes`` is the number of bytes of packets buffered in the range
+ starting from the current decoding position.
+
+ When querying the property with the client API using ``MPV_FORMAT_NODE``,
+ or with Lua ``mp.get_property_native``, this will return a mpv_node with
+ the following contents:
+
+ ::
+
+ MPV_FORMAT_NODE_MAP
+ "seekable-ranges" MPV_FORMAT_NODE_ARRAY
+ MPV_FORMAT_NODE_MAP
+ "start" MPV_FORMAT_DOUBLE
+ "end" MPV_FORMAT_DOUBLE
+ "fw-bytes" MPV_FORMAT_INT64
+
+ Other fields (might be changed or removed in the future):
+
+ ``eof``
+ True if the reader thread has hit the end of the file.
+
+ ``underrun``
+ True if the reader thread could not satisfy a decoder's request for a
+ new packet.
+
+ ``idle``
+ True if the thread is currently not reading.
+
+ ``total-bytes``
+ Sum of packet bytes (plus some overhead estimation) of the entire packet
+ queue, including cached seekable ranges.
+
+ ``fw-bytes``
+ Sum of packet bytes (plus some overhead estimation) of the readahead
+ packet queue (packets between current decoder reader positions and
+ demuxer position).
+
``demuxer-via-network``
Returns ``yes`` if the stream demuxed via the main demuxer is most likely
played via network. What constitutes "network" is not always clear, might
@@ -1287,8 +1338,8 @@ Property list
``seeking``
Returns ``yes`` if the player is currently seeking, or otherwise trying
to restart playback. (It's possible that it returns ``yes`` while a file
- is loaded, or when switching ordered chapter segments. This is because
- the same underlying code is used for seeking and resyncing.)
+ is loadedThis is because the same underlying code is used for seeking and
+ resyncing.)
``mixer-active``
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
@@ -1386,7 +1437,7 @@ Property list
This is known only once the VO has opened (and possibly later). With some
VOs (like ``opengl``), this might be never known in advance, but only when
the decoder attempted to create the hw decoder successfully. (Using
- ``--opengl-hwdec-interop`` can load it eagerly.) If there are multiple
+ ``--gpu-hwdec-interop`` can load it eagerly.) If there are multiple
drivers loaded, they will be separated by ``,``.
If no VO is active or no interop driver is known, this property is
@@ -1396,9 +1447,6 @@ Property list
multiple interop drivers for the same hardware decoder, depending on
platform and VO.
- This is somewhat similar to the ``--opengl-hwdec-interop`` option, but
- it returns the actually loaded backend, not the value of this option.
-
``video-format``
Video format as string.
@@ -2017,10 +2065,6 @@ Property list
``current-ao``
Current audio output driver (name as used with ``--ao``).
-``audio-out-detected-device``
- Return the audio device selected by the AO driver (only implemented for
- some drivers: currently only ``coreaudio``).
-
``working-directory``
Return the working directory of the mpv process. Can be useful for JSON IPC
users, because the command line player usually works with relative paths.
diff --git a/DOCS/man/javascript.rst b/DOCS/man/javascript.rst
index b935370..2ebdb15 100644
--- a/DOCS/man/javascript.rst
+++ b/DOCS/man/javascript.rst
@@ -164,10 +164,14 @@ Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
``mp.msg.debug(...)``
+``mp.msg.trace(...)``
+
``mp.utils.getcwd()`` (LE)
``mp.utils.readdir(path [, filter])`` (LE)
+``mp.utils.file_info(path)`` (LE)
+
``mp.utils.split_path(path)``
``mp.utils.join_path(p1, p2)``
@@ -222,7 +226,7 @@ Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
``exit()`` (global)
Make the script exit at the end of the current event loop iteration.
- Note: please reomve added key bindings before calling ``exit()``.
+ Note: please remove added key bindings before calling ``exit()``.
``mp.utils.compile_js(fname, content_str)``
Compiles the JS code ``content_str`` as file name ``fname`` (without loading
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index cfd622b..2e354c2 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -39,7 +39,10 @@ option. Some scripts are loaded internally (like ``--osc``). Each script runs in
its own thread. Your script is first run "as is", and once that is done, the event loop
is entered. This event loop will dispatch events received by mpv and call your
own event handlers which you have registered with ``mp.register_event``, or
-timers added with ``mp.add_timeout`` or similar.
+timers added with ``mp.add_timeout`` or similar. Note that since the
+script starts execution concurrently with player initialization, some properties
+may not be populated with meaningful values until the relevant subsystems have
+initialized.
When the player quits, all scripts will be asked to terminate. This happens via
a ``shutdown`` event, which by default will make the event loop return. If your
@@ -484,16 +487,17 @@ with ``require 'mp.msg'``.
``msg.log(level, ...)``
The level parameter is the message priority. It's a string and one of
- ``fatal``, ``error``, ``warn``, ``info``, ``v``, ``debug``. The user's
- settings will determine which of these messages will be visible. Normally,
- all messages are visible, except ``v`` and ``debug``.
+ ``fatal``, ``error``, ``warn``, ``info``, ``v``, ``debug``, ``trace``. The
+ user's settings will determine which of these messages will be
+ visible. Normally, all messages are visible, except ``v``, ``debug`` and
+ ``trace``.
The parameters after that are all converted to strings. Spaces are inserted
to separate multiple parameters.
You don't need to add newlines.
-``msg.fatal(...)``, ``msg.error(...)``, ``msg.warn(...)``, ``msg.info(...)``, ``msg.verbose(...)``, ``msg.debug(...)``
+``msg.fatal(...)``, ``msg.error(...)``, ``msg.warn(...)``, ``msg.info(...)``, ``msg.verbose(...)``, ``msg.debug(...)``, ``msg.trace(...)``
All of these are shortcuts and equivalent to the corresponding
``msg.log(level, ...)`` call.
@@ -588,6 +592,34 @@ strictly part of the guaranteed API.
On error, ``nil, error`` is returned.
+``utils.file_info(path)``
+ Stats the given path for information and returns a table with the
+ following entries:
+
+ ``mode``
+ protection bits (on Windows, always 755 (octal) for directories
+ and 644 (octal) for files)
+ ``size``
+ size in bytes
+ ``atime``
+ time of last access
+ ``mtime``
+ time of last modification
+ ``ctime``
+ time of last metadata change (Linux) / time of creation (Windows)
+ ``is_file``
+ Whether ``path`` is a regular file (boolean)
+ ``is_dir``
+ Whether ``path`` is a directory (boolean)
+
+ ``mode`` and ``size`` are integers.
+ Timestamps (``atime``, ``mtime`` and ``ctime``) are integer seconds since
+ the Unix epoch (Unix time).
+ The booleans ``is_file`` and ``is_dir`` are provided as a convenience;
+ they can be and are derived from ``mode``.
+
+ On error (eg. path does not exist), ``nil, error`` is returned.
+
``utils.split_path(path)``
Split a path into directory component and filename component, and return
them. The first return value is always the directory. The second return
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index 7202e32..12c3ec8 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -442,7 +442,7 @@ this is strongly discouraged and deprecated, except for ``-set``.
Without suffix, the action taken is normally ``-set``.
-Some options (like ``--sub-file``, ``--audio-file``, ``--opengl-shader``) are
+Some options (like ``--sub-file``, ``--audio-file``, ``--glsl-shader``) are
aliases for the proper option with ``-append`` action. For example,
``--sub-file`` is an alias for ``--sub-files-append``.
@@ -510,8 +510,8 @@ setting them to *no*. Even suboptions can be specified in this way.
::
- # Use opengl video output by default.
- vo=opengl
+ # Use GPU-accelerated video output by default.
+ vo=gpu
# Use quotes for text that can contain spaces:
status-msg="Time: ${time-pos}"
@@ -582,7 +582,7 @@ profile name ``default`` to continue with normal options.
[slow]
profile-desc="some profile name"
# reference a builtin profile
- profile=opengl-hq
+ profile=gpu-hq
[fast]
vo=vdpau
@@ -683,8 +683,10 @@ listed.
(``drop-frame-count`` property.)
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
The first value shows the amount of video buffered in the demuxer in seconds,
- the second value shows *additional* data buffered in the stream cache in
- kilobytes. (``demuxer-cache-duration`` and ``cache-used`` properties.)
+ the second value shows the sum of the demuxer forward cache size and the
+ *additional* data buffered in the stream cache in kilobytes.
+ (``demuxer-cache-duration``, ``demuxer-cache-state``, ``cache-used``
+ properties.)
PROTOCOLS
@@ -733,6 +735,8 @@ PROTOCOLS
``<number>`` (select playlist with the same index). You can list
the available playlists with ``--msg-level=bd=v``.
+ ``bluray://`` is an alias.
+
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
Play a DVD. DVD menus are not supported. If no title is given, the longest
@@ -877,6 +881,8 @@ works like in older mpv releases. The profiles are currently defined as follows:
.. include:: osc.rst
+.. include:: stats.rst
+
.. include:: lua.rst
.. include:: javascript.rst
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index ce14d3e..17495d6 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -30,6 +30,9 @@ Track Selection
- ``mpv --slang=jpn example.mkv`` plays a Matroska file with Japanese
subtitles.
+``--vlang=<...>``
+ Equivalent to ``--alang`` and ``--slang``, for video tracks.
+
``--aid=<ID|auto|no>``
Select audio track. ``auto`` selects the default, ``no`` disables audio.
See also ``--alang``. mpv normally prints available audio tracks on the
@@ -70,6 +73,8 @@ Track Selection
options), there will be streams with duplicate IDs. In this case, the
first stream in order is selected.
+ Deprecated.
+
``--edition=<ID|auto>``
(Matroska files only)
Specify the edition (set of chapters) to use, where 0 is the first. If set
@@ -104,6 +109,8 @@ Playback Control
``#c`` seeks to chapter number c. (Chapters start from 1.)
+ ``none`` resets any previously set option (useful for libmpv).
+
.. admonition:: Examples
``--start=+56``, ``--start=+00:56``
@@ -130,6 +137,9 @@ Playback Control
Stop after a given time relative to the start time.
See ``--start`` for valid option values and examples.
+ If both ``--end`` and ``--length`` are provided, playback will stop when it
+ reaches either of the two endpoints.
+
``--rebase-start-time=<yes|no>``
Whether to move the file start time to ``00:00:00`` (default: yes). This
is less awkward for files which start at a random timestamp, such as
@@ -316,8 +326,9 @@ Playback Control
the ``a`` timestamp. Seeking past the ``b`` point doesn't loop (this is
intentional).
- If both options are set to ``no``, looping is disabled. Otherwise, the
- start/end of the file is used if one of the options is set to ``no``.
+ If both options are set to ``no`` or unset, looping is disabled.
+ Otherwise, the start/end of playback is used if one of the options
+ is set to ``no`` or unset.
The loop-points can be adjusted at runtime with the corresponding
properties. See also ``ab-loop`` command.
@@ -356,11 +367,10 @@ Playback Control
Without ``--hr-seek``, skipping will snap to keyframes.
``--stop-playback-on-init-failure=<yes|no>``
- Stop playback if either audio or video fails to initialize. Currently,
- the default behavior is ``no`` for the command line player, but ``yes``
- for libmpv. With ``no``, playback will continue in video-only or audio-only
- mode if one of them fails. This doesn't affect playback of audio-only or
- video-only files.
+ Stop playback if either audio or video fails to initialize (default: no).
+ With ``no``, playback will continue in video-only or audio-only mode if one
+ of them fails. This doesn't affect playback of audio-only or video-only
+ files.
Program Behavior
----------------
@@ -403,8 +413,9 @@ Program Behavior
``--log-file=<path>``
Opens the given path for writing, and print log messages to it. Existing
- files will be truncated. The log level always corresponds to ``-v``,
- regardless of terminal verbosity levels.
+ files will be truncated. The log level is at least ``-v -v``, but
+ can be raised via ``--msg-level`` (the option cannot lower it below the
+ forced minimum log level).
``--config-dir=<path>``
Force a different configuration directory. If this is set, the given
@@ -444,7 +455,7 @@ Program Behavior
``--idle=<no|yes|once>``
Makes mpv wait idly instead of quitting when there is no file to play.
Mostly useful in input mode, where mpv can be controlled through input
- commands.
+ commands. (Default: ``no``)
``once`` will only idle at start and let the player close once the
first playlist has finished playing back.
@@ -519,6 +530,7 @@ Program Behavior
``--ignore-path-in-watch-later-config``
Ignore path (i.e. use filename only) when using watch later feature.
+ (Default: disabled)
``--show-profile=<profile>``
Show the description and content of a profile.
@@ -579,6 +591,11 @@ Program Behavior
- ``--ytdl-raw-options=username=user,password=pass``
- ``--ytdl-raw-options=force-ipv6=``
+``--load-stats-overlay=<yes|no>``
+ Enable the builtin script that shows useful playback information on a key
+ binding (default: yes). By default, the ``i`` key is used (``I`` to make
+ the overlay permanent).
+
``--player-operation-mode=<cplayer|pseudo-gui>``
For enabling "pseudo GUI mode", which means that the defaults for some
options are changed. This option should not normally be used directly, but
@@ -667,54 +684,60 @@ Video
:auto: enable best hw decoder (see below)
:yes: exactly the same as ``auto``
:auto-copy: enable best hw decoder with copy-back (see below)
- :vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
+ :vdpau: requires ``--vo=gpu`` or ``--vo=vdpau`` (Linux only)
:vdpau-copy: copies video back into system RAM (Linux with some GPUs only)
- :vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
- :vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
- :videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up),
+ :vaapi: requires ``--vo=gpu`` or ``--vo=vaapi`` (Linux only)
+ :vaapi-copy: copies video back into system RAM (Linux with some GPUs only)
+ :videotoolbox: requires ``--vo=gpu`` (OS X 10.8 and up),
or ``--vo=opengl-cb`` (iOS 9.0 and up)
:videotoolbox-copy: copies video back into system RAM (OS X 10.8 or iOS 9.0 and up)
- :dxva2: requires ``--vo=opengl`` with ``--opengl-backend=angle`` or
- ``--opengl-backend=dxinterop`` (Windows only)
+ :dxva2: requires ``--vo=gpu`` with ``--gpu-context=angle`` or
+ ``--gpu-context=dxinterop`` (Windows only)
:dxva2-copy: copies video back to system RAM (Windows only)
- :d3d11va: requires ``--vo=opengl`` with ``--opengl-backend=angle``
- (Windows 8+ only)
+ :d3d11va: requires ``--vo=gpu`` with ``--gpu-context=d3d11`` or
+ ``--gpu-context=angle`` (Windows 8+ only)
:d3d11va-copy: copies video back to system RAM (Windows 8+ only)
- :mediacodec: copies video back to system RAM (Android only)
- :rpi: requires ``--vo=opengl`` (Raspberry Pi only - default if available)
- :rpi-copy: copies video back to system RAM (Raspberry Pi only)
- :cuda: requires ``--vo=opengl`` (Any platform CUDA is available)
+ :mediacodec: requires ``--vo=mediacodec_embed`` (Android only)
+ :mediacodec-copy: copies video back to system RAM (Android only)
+ :mmal: requires ``--vo=gpu`` (Raspberry Pi only - default if available)
+ :mmal-copy: copies video back to system RAM (Raspberry Pi only)
+ :cuda: requires ``--vo=gpu`` (Any platform CUDA is available)
:cuda-copy: copies video back to system RAM (Any platform CUDA is available)
+ :nvdec: requires ``--vo=gpu`` (Any platform CUDA is available)
+ :nvdec-copy: copies video back to system RAM (Any platform CUDA is available)
:crystalhd: copies video back to system RAM (Any platform supported by hardware)
+ :rkmpp: requires ``--vo=gpu`` (some RockChip devices only)
``auto`` tries to automatically enable hardware decoding using the first
available method. This still depends what VO you are using. For example,
- if you are not using ``--vo=vdpau`` or ``--vo=opengl``, vdpau decoding will
+ if you are not using ``--vo=gpu`` or ``--vo=vdpau``, vdpau decoding will
never be enabled. Also note that if the first found method doesn't actually
work, it will always fall back to software decoding, instead of trying the
next method (might matter on some Linux systems).
``auto-copy`` selects only modes that copy the video data back to system
- memory after decoding. Currently, this selects only one of the following
- modes: ``vaapi-copy``, ``dxva2-copy``, ``d3d11va-copy``, ``mediacodec``.
+ memory after decoding. This selects modes like ``vaapi-copy`` (and so on).
If none of these work, hardware decoding is disabled. This mode is always
guaranteed to incur no additional loss compared to software decoding, and
will allow CPU processing with video filters.
- The ``vaapi`` mode, if used with ``--vo=opengl``, requires Mesa 11 and most
- likely works with Intel GPUs only. It also requires the opengl EGL backend
- (automatically used if available). You can also try the old GLX backend by
- forcing it with ``--opengl-backend=x11``, but the vaapi/GLX interop is
- said to be slower than ``vaapi-copy``.
+ The ``vaapi`` mode, if used with ``--vo=gpu``, requires Mesa 11 and most
+ likely works with Intel GPUs only. It also requires the opengl EGL backend.
The ``cuda`` and ``cuda-copy`` modes provides deinterlacing in the decoder
which is useful as there is no other deinterlacing mechanism in the opengl
output path. To use this deinterlacing you must pass the option:
``vd-lavc-o=deint=[weave|bob|adaptive]``.
Pass ``weave`` (or leave the option unset) to not attempt any
- deinterlacing. ``cuda`` should always be preferred unless the ``opengl``
+ deinterlacing. ``cuda`` should always be preferred unless the ``gpu``
vo is not being used or filters are required.
+ ``nvdec`` is a newer implementation of CUVID/CUDA decoding, which uses the
+ FFmpeg decoders for file parsing. Experimental, is known not to correctly
+ check whether decoding is supported by the hardware at all. Deinterlacing
+ is not supported. Since this uses FFmpeg's codec parsers, it is expected
+ that this generally causes fewer issues than ``cuda``.
+
Most video filters will not work with hardware decoding as they are
primarily implemented on the CPU. Some exceptions are ``vdpaupp``,
``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details.
@@ -739,8 +762,8 @@ Video
be some loss, or even blatantly incorrect results.
In some cases, RGB conversion is forced, which means the RGB conversion
- is performed by the hardware decoding API, instead of the OpenGL code
- used by ``--vo=opengl``. This means certain colorspaces may not display
+ is performed by the hardware decoding API, instead of the shaders
+ used by ``--vo=gpu``. This means certain colorspaces may not display
correctly, and certain filtering (such as debanding) cannot be applied
in an ideal way. This will also usually force the use of low quality
chroma scalers instead of the one specified by ``--cscale``. In other
@@ -759,10 +782,11 @@ Video
BT.601 or BT.709, a forced, low-quality but correct RGB conversion is
performed. Otherwise, the result will be totally incorrect.
- ``d3d11va`` is usually safe (if used with ANGLE builds that support
- ``EGL_KHR_stream path`` - otherwise, it converts to RGB), except that
- 10 bit input (HEVC main 10 profiles) will be rounded down to 8 bits,
- which results in reduced quality.
+ ``d3d11va`` is safe when used with the ``d3d11`` backend. If used with
+ ``angle`` is it usually safe, except that 10 bit input (HEVC main 10
+ profiles) will be rounded down to 8 bits, which will result in reduced
+ quality. Also note that with very old ANGLE builds (without
+ ``EGL_KHR_stream path``,) all input will be converted to RGB.
``dxva2`` is not safe. It appears to always use BT.601 for forced RGB
conversion, but actual behavior depends on the GPU drivers. Some drivers
@@ -772,7 +796,7 @@ Video
completely ordinary video sources.
``rpi`` always uses the hardware overlay renderer, even with
- ``--vo=opengl``.
+ ``--vo=gpu``.
``cuda`` should be safe, but it has been reported to corrupt the
timestamps causing glitched, flashing frames on some files. It can also
@@ -799,34 +823,44 @@ Video
frame glitches or discoloration, and you have ``--hwdec`` turned on,
the first thing you should try is disabling it.
-``--opengl-hwdec-interop=<name>``
- This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
- hardware decoding OpenGL interop context, but without actually enabling
- hardware decoding itself (like ``--hwdec`` does).
+``--gpu-hwdec-interop=<auto|all|no|name>``
+ This option is for troubleshooting hwdec interop issues. Since it's a
+ debugging option, its semantics may change at any time.
+
+ This is useful for the ``gpu`` and ``opengl-cb`` VOs for selecting which
+ hwdec interop context to use exactly. Effectively it also can be used
+ to block loading of certain backends.
- If set to an empty string (default), the ``--hwdec`` option is used.
+ If set to ``auto`` (default), the behavior depends on the VO: for ``gpu``,
+ it does nothing, and the interop context is loaded on demand (when the
+ decoder probes for ``--hwdec`` support). For ``opengl-cb``, which has
+ has no on-demand loading, this is equivalent to ``all``.
- For ``opengl``, if set, do not create the interop context on demand, but
- when the VO is created.
+ The empty string is equivalent to ``auto``.
- For ``opengl-cb``, if set, load the interop context as soon as the OpenGL
- context is created. Since ``opengl-cb`` has no on-demand loading, this
- allows enabling hardware decoding at runtime at all, without having
- to temporarily set the ``hwdec`` option just during OpenGL context
- initialization with ``mpv_opengl_cb_init_gl()``.
+ If set to ``all``, it attempts to load all interop contexts at GL context
+ creation time.
- See ``--opengl-hwdec-interop=help`` for accepted values. This lists the
- interop backend, with the ``--hwdec`` alias after it in ``[...]``. Consider
- all values except the proper interop backend name, ``auto``, and ``no`` as
- silently deprecated and subject to change. Also, if you use this in
- application code (e.g. via libmpv), any value other than ``auto`` and ``no``
- should be avoided, as backends can change.
+ Other than that, a specific backend can be set, and the list of them can
+ be queried with ``help`` (mpv CLI only).
- Currently the option sets a single value. It is possible that the option
- type changes to a list in the future.
+ Runtime changes to this are ignored (the current option value is used
+ whenever the renderer is created).
- The old alias ``--hwdec-preload`` has different behavior if the option value
- is ``no``.
+ The old aliases ``--opengl-hwdec-interop`` and ``--hwdec-preload`` are
+ barely related to this anymore, but will be somewhat compatible in some
+ cases.
+
+``--hwdec-image-format=<name>``
+ Set the internal pixel format used by hardware decoding via ``--hwdec``
+ (default ``no``). The special value ``no`` selects an implementation
+ specific standard format. Most decoder implementations support only one
+ format, and will fail to initialize if the format is not supported.
+
+ Some implementations might support multiple formats. In particular,
+ videotoolbox is known to require ``uyvy422`` for good performance on some
+ older hardware. d3d11va can always use ``yuv420p``, which uses an opaque
+ format, with likely no advantages.
``--videotoolbox-format=<name>``
Set the internal pixel format used by ``--hwdec=videotoolbox`` on OSX. The
@@ -838,6 +872,9 @@ Video
Since mpv 0.25.0, ``no`` is an accepted value, which lets the decoder pick
the format on newer FFmpeg versions (will use ``nv12`` on older versions).
+ Deprecated. Use ``--hwdec-image-format`` if you really need this. If both
+ are specified, ``--hwdec-image-format`` wins.
+
``--panscan=<0.0-1.0>``
Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
video to make it fit a 4:3 display without black bands). The range
@@ -916,8 +953,8 @@ Video
rotation metadata.)
``--video-stereo-mode=<no|mode>``
- Set the stereo 3D output mode (default: ``mono``). This is done by inserting
- the ``stereo3d`` conversion filter.
+ Set the stereo 3D output mode (default: ``mono``). This is mostly broken and
+ thus deprecated.
The pseudo-mode ``no`` disables automatic conversion completely.
@@ -1049,7 +1086,7 @@ Video
This can speed up video upload, and may help with large resolutions or
slow hardware. This works only with the following VOs:
- - ``opengl``: requires at least OpenGL 4.4.
+ - ``gpu``: requires at least OpenGL 4.4.
(In particular, this can't be made work with ``opengl-cb``.)
@@ -1193,11 +1230,11 @@ Audio
List of codecs for which compressed audio passthrough should be used. This
works for both classic S/PDIF and HDMI.
- Possible codecs are ``ac3``, ``dts``, ``dts-hd``. Multiple codecs can be
- specified by separating them with ``,``. ``dts`` refers to low bitrate DTS
- core, while ``dts-hd`` refers to DTS MA (receiver and OS support varies).
- If both ``dts`` and ``dts-hd`` are specified, it behaves equivalent to
- specifying ``dts-hd`` only.
+ Possible codecs are ``ac3``, ``dts``, ``dts-hd``, ``eac3``, ``truehd``.
+ Multiple codecs can be specified by separating them with ``,``. ``dts``
+ refers to low bitrate DTS core, while ``dts-hd`` refers to DTS MA (receiver
+ and OS support varies). If both ``dts`` and ``dts-hd`` are specified, it
+ behaves equivalent to specifying ``dts-hd`` only.
In earlier mpv versions you could use ``--ad`` to force the spdif wrapper.
This does not work anymore.
@@ -1258,14 +1295,6 @@ Audio
is always applied if the replaygain logic is somehow inactive. If this
is applied, no other replaygain options are applied.
-``--balance=<value>``
- How much left/right channels contribute to the audio. (The implementation
- of this feature is rather odd. It doesn't change the volumes of each
- channel, but instead sets up a pan matrix to mix the left and right
- channels.)
-
- Deprecated.
-
``--audio-delay=<sec>``
Audio delay in seconds (positive or negative float value). Positive values
delay the audio, and negative values delay the video.
@@ -1378,6 +1407,10 @@ Audio
If the channel layout of the media file (i.e. the decoder) and the AO's
channel layout don't match, mpv will attempt to insert a conversion filter.
+ You may need to change the channel layout of the system mixer to achieve
+ your desired output as mpv does not have control over it. Another
+ work-around for this on some AOs is to use ``--audio-exclusive=yes`` to
+ circumvent the system mixer entirely.
.. admonition:: Warning
@@ -1554,7 +1587,7 @@ Subtitles
``--sub-delay=<sec>``
Delays subtitles by ``<sec>`` seconds. Can be negative.
-``--sub-files=<file-list>``
+``--sub-files=<file-list>``, ``--sub-file=<filename>``
Add a subtitle file to the list of external subtitles.
If you use ``--sub-file`` only once, this subtitle file is displayed by
@@ -1566,7 +1599,11 @@ Subtitles
and ``--secondary-sid`` to select the second index. (The index is printed
on the terminal output after the ``--sid=`` in the list of streams.)
- This is a list option. See `List Options`_ for details.
+ ``--sub-files`` is a list option (see `List Options`_ for details), and
+ can take multiple file names separated by ``:`` (Unix) or ``;`` (Windows),
+ while ``--sub-file`` takes a single filename, but can be used multiple
+ times to add multiple files. Technically, ``--sub-file`` is a CLI/config
+ file only alias for ``--sub-files-append``.
``--secondary-sid=<ID|auto|no>``
Select a secondary subtitle stream. This is similar to ``--sid``. If a
@@ -2104,6 +2141,18 @@ Subtitles
Default: ``no``.
+``--sub-create-cc-track=<yes|no>``
+ For every video stream, create a closed captions track (default: no). The
+ only purpose is to make the track available for selection at the start of
+ playback, instead of creating it lazily. This applies only to
+ ``ATSC A53 Part 4 Closed Captions`` (displayed by mpv as subtitle tracks
+ using the codec ``eia_608``). The CC track is marked "default" and selected
+ according to the normal subtitle track selection rules. You can then use
+ ``--sid`` to explicitly select the correct track too.
+
+ If the video stream contains no closed captions, or if no video is being
+ decoded, the CC track will remain empty and will not show any text.
+
Window
------
@@ -2210,6 +2259,13 @@ Window
This option does not affect the framerate used for ``mf://`` or
``--merge-files``. For that, use ``--mf-fps`` instead.
+ Setting ``--image-display-duration`` hides the OSC and does not track
+ playback time on the command-line output, and also does not duplicate
+ the image frame when encoding. To force the player into "dumb mode"
+ and actually count out seconds, or to duplicate the image when
+ encoding, you need to use ``--demuxer=lavf --demuxer-lavf-o=loop=1``,
+ and use ``--length`` or ``--frames`` to stop after a particular time.
+
``--force-window=<yes|no|immediate>``
Create a video output window even if there is no video. This can be useful
when pretending that mpv is a GUI application. Currently, the window
@@ -2402,8 +2458,8 @@ Window
``--force-rgba-osd-rendering``
Change how some video outputs render the OSD and text subtitles. This
does not change appearance of the subtitles and only has performance
- implications. For VOs which support native ASS rendering (like ``vdpau``,
- ``opengl``, ``direct3d``), this can be slightly faster or slower,
+ implications. For VOs which support native ASS rendering (like ``gpu``,
+ ``vdpau``, ``direct3d``), this can be slightly faster or slower,
depending on GPU drivers and hardware. For other VOs, this just makes
rendering slower.
@@ -2412,50 +2468,6 @@ Window
there is a change in video parameters, video stream or file. This used to
be the default behavior. Currently only affects X11 VOs.
-``--heartbeat-cmd=<command>``
-
- .. warning::
-
- This option is redundant with Lua scripting. Further, it shouldn't be
- needed for disabling screensaver anyway, since mpv will call
- ``xdg-screensaver`` when using X11 backend. As a consequence this
- option has been deprecated with no direct replacement.
-
- Command that is executed every 30 seconds during playback via *system()* -
- i.e. using the shell. The time between the commands can be customized with
- the ``--heartbeat-interval`` option. The command is not run while playback
- is paused.
-
- .. note::
-
- mpv uses this command without any checking. It is your responsibility to
- ensure it does not cause security problems (e.g. make sure to use full
- paths if "." is in your path like on Windows). It also only works when
- playing video (i.e. not with ``--no-video`` but works with
- ``--vo=null``).
-
- This can be "misused" to disable screensavers that do not support the
- proper X API (see also ``--stop-screensaver``). If you think this is too
- complicated, ask the author of the screensaver program to support the
- proper X APIs. Note that the ``--stop-screensaver`` does not influence the
- heartbeat code at all.
-
- .. admonition:: Example for xscreensaver
-
- ``mpv --heartbeat-cmd="xscreensaver-command -deactivate" file``
-
- .. admonition:: Example for GNOME screensaver
-
- ``mpv --heartbeat-cmd="gnome-screensaver-command --deactivate" file``
-
-
-``--heartbeat-interval=<sec>``
- Time between ``--heartbeat-cmd`` invocations in seconds (default: 30).
-
- .. note::
-
- This does not affect the normal screensaver operation in any way.
-
``--no-keepaspect``, ``--keepaspect``
``--no-keepaspect`` will always stretch the video to window size, and will
disable the window manager hints that force the window aspect ratio.
@@ -2526,6 +2538,11 @@ Window
support window embedding of foreign processes, this works only with libmpv,
and will crash when used from the command line.
+ On Android, the ID is interpreted as ``android.view.Surface``. Pass it as a
+ value cast to ``intptr_t``. Use with ``--vo=mediacodec_embed`` and
+ ``--hwdec=mediacodec`` for direct rendering using MediaCodec, or with
+ ``--vo=gpu --gpu-context=android`` (with or without ``--hwdec=mediacodec-copy``).
+
``--no-window-dragging``
Don't move the window when clicking on it and moving the mouse pointer.
@@ -2857,11 +2874,38 @@ Demuxer
See ``--list-options`` for defaults and value range.
-``--demuxer-max-packets=<packets>``
- Quite similar ``--demuxer-max-bytes=<bytes>``. Deprecated, because the
- other option does basically the same job. Since mpv 0.25.0, the code
- tries to account for per-packet overhead, which is why this option becomes
- rather pointless.
+``--demuxer-max-back-bytes=<value>``
+ This controls how much past data the demuxer is allowed to preserve. This
+ is useful only if the ``--demuxer-seekable-cache`` option is enabled.
+ Unlike the forward cache, there is no control how many seconds are actually
+ cached - it will simply use as much memory this option allows. Setting this
+ option to 0 will strictly disable any back buffer, but this will lead to
+ the situation that the forward seek range starts after the current playback
+ position (as it removes past packets that are seek points).
+
+ Keep in mind that other buffers in the player (like decoders) will cause the
+ demuxer to cache "future" frames in the back buffer, which can skew the
+ impression about how much data the backbuffer contains.
+
+ See ``--list-options`` for defaults and value range.
+
+``--demuxer-seekable-cache=<yes|no|auto>``
+ This controls whether seeking can use the demuxer cache (default: auto). If
+ enabled, short seek offsets will not trigger a low level demuxer seek
+ (which means for example that slow network round trips or FFmpeg seek bugs
+ can be avoided). If a seek cannot happen within the cached range, a low
+ level seek will be triggered. Seeking outside of the cache will start a new
+ cached range, but can discard the old cache range if the demuxer exhibits
+ certain unsupported behavior.
+
+ Keep in mind that some events can flush the cache or force a low level
+ seek anyway, such as switching tracks, or attempting to seek before the
+ start or after the end of the file. This option is experimental - thus
+ disabled, and bugs are to be expected.
+
+ The special value ``auto`` means ``yes`` in the same situation as
+ ``--cache-secs`` is used (i.e. when the stream appears to be a network
+ stream or the stream cache is enabled).
``--demuxer-thread=<yes|no>``
Run the demuxer in a separate thread, and let it prefetch a certain amount
@@ -3421,12 +3465,16 @@ Terminal
``--msg-level=<module1=level1,module2=level2,...>``
Control verbosity directly for each module. The ``all`` module changes the
- verbosity of all the modules not explicitly specified on the command line.
+ verbosity of all the modules. The verbosity changes from this option are
+ applied in order from left to right, and each item can override a previous
+ one.
Run mpv with ``--msg-level=all=trace`` to see all messages mpv outputs. You
can use the module names printed in the output (prefixed to each line in
``[...]``) to limit the output to interesting modules.
+ This also affects ``--log-file``, and in certain cases libmpv API logging.
+
.. note::
Some messages are printed before the command line is parsed and are
@@ -3708,7 +3756,7 @@ Cache
between readahead and backbuffer sizes.
``--cache-default=<kBytes|no>``
- Set the size of the cache in kilobytes (default: 75000 KB). Using ``no``
+ Set the size of the cache in kilobytes (default: 10000 KB). Using ``no``
will not automatically enable the cache e.g. when playing from a network
stream. Note that using ``--cache`` will always override this option.
@@ -3729,7 +3777,7 @@ Cache
This option allows control over this.
``--cache-backbuffer=<kBytes>``
- Size of the cache back buffer (default: 75000 KB). This will add to the total
+ Size of the cache back buffer (default: 10000 KB). This will add to the total
cache size, and reserved the amount for seeking back. The reserved amount
will not be used for readahead, and instead preserves already read data to
enable fast seeking back.
@@ -3787,7 +3835,9 @@ Cache
``--cache-secs=<seconds>``
How many seconds of audio/video to prefetch if the cache is active. This
overrides the ``--demuxer-readahead-secs`` option if and only if the cache
- is enabled and the value is larger. (Default: 10.)
+ is enabled and the value is larger. The default value is set to something
+ very high, so the actually achieved readahead will usually be limited by
+ the value of the ``--demuxer-max-bytes`` option.
``--cache-pause``, ``--no-cache-pause``
Whether the player should automatically pause when the cache runs low,
@@ -3947,10 +3997,10 @@ ALSA audio output options
ALSA device).
-OpenGL renderer options
+GPU renderer options
-----------------------
-The following video options are currently all specific to ``--vo=opengl`` and
+The following video options are currently all specific to ``--vo=gpu`` and
``--vo=opengl-cb`` only, which are the only VOs that implement them.
``--scale=<filter>``
@@ -3961,7 +4011,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
is the default for compatibility reasons.
``spline36``
- Mid quality and speed. This is the default when using ``opengl-hq``.
+ Mid quality and speed. This is the default when using ``gpu-hq``.
``lanczos``
Lanczos scaling. Provides mid quality and speed. Generally worse than
@@ -4019,9 +4069,11 @@ The following video options are currently all specific to ``--vo=opengl`` and
``--tscale`` are separable convolution filters (use ``--tscale=help`` to
get a list). The default is ``mitchell``.
- Note that the maximum supported filter radius is currently 3, due to
- limitations in the number of video textures that can be loaded
- simultaneously.
+ Common ``--tscale`` choices include ``oversample``, ``linear``,
+ ``catmull_rom``, ``mitchell``, ``gaussian``, or ``bicubic``. These are
+ listed in increasing order of smoothness/blurriness, with ``bicubic``
+ being the smoothest/blurriest and ``oversample`` being the sharpest/least
+ smooth.
``--scale-param1=<value>``, ``--scale-param2=<value>``, ``--cscale-param1=<value>``, ``--cscale-param2=<value>``, ``--dscale-param1=<value>``, ``--dscale-param2=<value>``, ``--tscale-param1=<value>``, ``--tscale-param2=<value>``
Set filter parameters. Ignored if the filter is not tunable. Currently,
@@ -4124,7 +4176,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
``--linear-scaling``
Scale in linear light. It should only be used with a
- ``--opengl-fbo-format`` that has at least 16 bit precision. This option
+ ``--fbo-format`` that has at least 16 bit precision. This option
has no effect on HDR content.
``--correct-downscaling``
@@ -4147,10 +4199,6 @@ The following video options are currently all specific to ``--vo=opengl`` and
the video along the temporal axis. The filter used can be controlled using
the ``--tscale`` setting.
- Note that this relies on vsync to work, see ``--opengl-swapinterval`` for
- more information. It should also only be used with an ``--opengl-fbo-format``
- that has at least 16 bit precision.
-
``--interpolation-threshold=<0..1,-1>``
Threshold below which frame ratio interpolation gets disabled (default:
``0.0001``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
@@ -4212,10 +4260,10 @@ The following video options are currently all specific to ``--vo=opengl`` and
``--temporal-dither`` is in use. 1 (the default) will update on every video
frame, 2 on every other frame, etc.
-``--opengl-debug``
- Check for OpenGL errors, i.e. call ``glGetError()``. Also, request a
- debug OpenGL context (which does nothing with current graphics drivers
- as of this writing).
+``--gpu-debug``
+ Enables GPU debugging. What this means depends on the API type. For OpenGL,
+ it calls ``glGetError()``, and requests a debug context. For Vulkan, it
+ enables validation layers.
``--opengl-swapinterval=<n>``
Interval in displayed frames between two buffer swaps. 1 is equivalent to
@@ -4228,7 +4276,83 @@ The following video options are currently all specific to ``--vo=opengl`` and
results, as can missing or incorrect display FPS information (see
``--display-fps``).
-``--opengl-shaders=<file-list>``
+``--vulkan-swap-mode=<mode>``
+ Controls the presentation mode of the vulkan swapchain. This is similar
+ to the ``--opengl-swapinterval`` option.
+
+ auto
+ Use the preferred swapchain mode for the vulkan context. (Default)
+ fifo
+ Non-tearing, vsync blocked. Similar to "VSync on".
+ fifo-relaxed
+ Tearing, vsync blocked. Late frames will tear instead of stuttering.
+ mailbox
+ Non-tearing, not vsync blocked. Similar to "triple buffering".
+ immediate
+ Tearing, not vsync blocked. Similar to "VSync off".
+
+``--vulkan-queue-count=<1..8>``
+ Controls the number of VkQueues used for rendering (limited by how many
+ your device supports). In theory, using more queues could enable some
+ parallelism between frames (when using a ``--swapchain-depth`` higher than
+ 1). (Default: 1)
+
+ NOTE: Setting this to a value higher than 1 may cause graphical corruption,
+ as mpv's vulkan implementation currently does not try and protect textures
+ against concurrent access.
+
+``--d3d11-warp=<yes|no|auto>``
+ Use WARP (Windows Advanced Rasterization Platform) with the D3D11 GPU
+ backend (default: auto). This is a high performance software renderer. By
+ default, it is only used when the system has no hardware adapters that
+ support D3D11. While the extended GPU features will work with WARP, they
+ can be very slow.
+
+``--d3d11-feature-level=<12_1|12_0|11_1|11_0|10_1|10_0|9_3|9_2|9_1>``
+ Select a specific feature level when using the D3D11 GPU backend. By
+ default, the highest available feature level is used. This option can be
+ used to select a lower feature level, which is mainly useful for debugging.
+ Most extended GPU features will not work at 9_x feature levels.
+
+``--d3d11-flip=<yes|no>``
+ Enable flip-model presentation, which avoids unnecessarily copying the
+ backbuffer by sharing surfaces with the DWM (default: yes). This may cause
+ performance issues with older drivers. If flip-model presentation is not
+ supported (for example, on Windows 7 without the platform update), mpv will
+ automatically fall back to the older bitblt presentation model.
+
+``--d3d11-sync-interval=<0..4>``
+ Schedule each frame to be presented for this number of VBlank intervals.
+ (default: 1) Setting to 1 will enable VSync, setting to 0 will disable it.
+
+``--d3d11va-zero-copy=<yes|no>``
+ By default, when using hardware decoding with ``--gpu-api=d3d11``, the
+ video image will be copied (GPU-to-GPU) from the decoder surface to a
+ shader resource. Set this option to avoid that copy by sampling directly
+ from the decoder image. This may increase performance and reduce power
+ usage, but can cause the image to be sampled incorrectly on the bottom and
+ right edges due to padding, and may invoke driver bugs, since Direct3D 11
+ technically does not allow sampling from a decoder surface (though most
+ drivers support it.)
+
+ Currently only relevant for ``--gpu-api=d3d11``.
+
+``--spirv-compiler=<compiler>``
+ Controls which compiler is used to translate GLSL to SPIR-V. This is
+ (currently) only relevant for ``--gpu-api=vulkan``. The possible choices
+ are:
+
+ auto
+ Use the first available compiler. (Default)
+ shaderc
+ Use libshaderc, which is an API wrapper around glslang. This is
+ generally the most preferred, if available.
+ nvidia
+ Use nvidia's built-in compiler. Only works for nvidia GPUs. Can be
+ buggy, but also supports some features glslang does not. Only works
+ with vulkan.
+
+``--glsl-shaders=<file-list>``
Custom GLSL hooks. These are a flexible way to add custom fragment shaders,
which can be injected at almost arbitrary points in the rendering pipeline,
and access all previous intermediate textures. Each use of the option will
@@ -4270,7 +4394,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
FORMAT <name> (required)
The texture format for the samples. Supported texture formats are listed
- in debug logging when the ``opengl`` VO is initialized (look for
+ in debug logging when the ``gpu`` VO is initialized (look for
``Texture formats:``). Usually, this follows OpenGL naming conventions.
For example, ``rgb16`` provides 3 channels with normalized 16 bit
components. One oddity are float formats: for example, ``rgba16f`` has
@@ -4413,8 +4537,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
vec2 tex_offset
Texture offset introduced by user shaders or options like panscan, video-align-x/y, video-pan-x/y.
- Internally, vo_opengl may generate any number of the following textures.
- Whenever a texture is rendered and saved by vo_opengl, all of the passes
+ Internally, vo_gpu may generate any number of the following textures.
+ Whenever a texture is rendered and saved by vo_gpu, all of the passes
that have hooked into it will run, in the order they were added by the
user. This is a list of the legal hook points:
@@ -4460,8 +4584,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
pass. When overwriting a texture marked ``fixed``, the WIDTH, HEIGHT and
OFFSET must be left at their default values.
-``--opengl-shader=<file>``
- CLI/config file only alias for ``--opengl-shaders-append``.
+``--glsl-shader=<file>``
+ CLI/config file only alias for ``--glsl-shaders-append``.
``--deband``
Enable the debanding algorithm. This greatly reduces the amount of visible
@@ -4514,9 +4638,9 @@ The following video options are currently all specific to ``--vo=opengl`` and
``--scale-blur`` option.
``--opengl-glfinish``
- Call ``glFinish()`` before and after swapping buffers (default: disabled).
- Slower, but might improve results when doing framedropping. Can completely
- ruin performance. The details depend entirely on the OpenGL driver.
+ Call ``glFinish()`` before swapping buffers (default: disabled). Slower,
+ but might improve results when doing framedropping. Can completely ruin
+ performance. The details depend entirely on the OpenGL driver.
``--opengl-waitvsync``
Call ``glXWaitVideoSyncSGI`` after each buffer swap (default: disabled).
@@ -4525,15 +4649,6 @@ The following video options are currently all specific to ``--vo=opengl`` and
X11/GLX only.
-``--opengl-vsync-fences=<N>``
- Synchronize the CPU to the Nth past frame using the ``GL_ARB_sync``
- extension. A value of 0 disables this behavior (default). A value of 1
- means it will synchronize to the current frame after rendering it. Like
- ``--glfinish`` and ``--waitvsync``, this can lower or ruin performance. Its
- advantage is that it can span multiple frames, and effectively limit the
- number of frames the GPU queues ahead (which also has an influence on
- vsync).
-
``--opengl-dwmflush=<no|windowed|yes|auto>``
Calls ``DwmFlush`` after swapping buffers on Windows (default: auto). It
also sets ``SwapInterval(0)`` to ignore the OpenGL timing. Values are: no
@@ -4554,7 +4669,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
used to select a lower feature level, which is mainly useful for debugging.
Note that OpenGL ES 3.0 is only supported at feature level 10_1 or higher.
Most extended OpenGL features will not work at lower feature levels
- (similar to ``--opengl-dumb-mode``).
+ (similar to ``--gpu-dumb-mode``).
Windows with ANGLE only.
@@ -4594,14 +4709,6 @@ The following video options are currently all specific to ``--vo=opengl`` and
Windows with ANGLE only.
-``--angle-max-frame-latency=<1-16>``
- Sets the maximum number of frames that the system is allowed to queue for
- rendering with the ANGLE backend (default: 3). Lower values should make
- VSync timing more accurate, but a value of ``1`` requires powerful
- hardware, since the CPU will not be able to "render ahead" of the GPU.
-
- Windows with ANGLE only.
-
``--angle-renderer=<d3d9|d3d11|auto>``
Forces a specific renderer when using the ANGLE backend (default: auto). In
auto mode this will pick D3D11 for systems that support Direct3D 11 feature
@@ -4610,18 +4717,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
renderer, though ``--angle-renderer=d3d9`` may give slightly better
performance on old hardware. Note that the D3D9 renderer only supports
OpenGL ES 2.0, so most extended OpenGL features will not work if this
- renderer is selected (similar to ``--opengl-dumb-mode``).
-
- Windows with ANGLE only.
-
-``--angle-swapchain-length=<2-16>``
- Sets the number of buffers in the D3D11 presentation queue when using the
- ANGLE backend (default: 6). At least 2 are required, since one is the back
- buffer that mpv renders to and the other is the front buffer that is
- presented by the DWM. Additional buffers can improve performance, because
- for example, mpv will not have to wait on the DWM to release the front
- buffer before rendering a new frame to it. For this reason, Microsoft
- recommends at least 4.
+ renderer is selected (similar to ``--gpu-dumb-mode``).
Windows with ANGLE only.
@@ -4631,13 +4727,21 @@ The following video options are currently all specific to ``--vo=opengl`` and
OS X only.
-``--opengl-sw``
+``--swapchain-depth=<N>``
+ Allow up to N in-flight frames. This essentially controls the frame
+ latency. Increasing the swapchain depth can improve pipelining and prevent
+ missed vsyncs, but increases visible latency. This option only mandates an
+ upper limit, the implementation can use a lower latency than requested
+ internally. A setting of 1 means that the VO will wait for every frame to
+ become visible before starting to render the next frame. (Default: 3)
+
+``--gpu-sw``
Continue even if a software renderer is detected.
-``--opengl-backend=<sys>``
- The value ``auto`` (the default) selects the windowing backend. You can
- also pass ``help`` to get a complete list of compiled in backends (sorted
- by autoprobe order).
+``--gpu-context=<sys>``
+ The value ``auto`` (the default) selects the GPU context. You can also pass
+ ``help`` to get a complete list of compiled in backends (sorted by
+ autoprobe order).
auto
auto-select (default)
@@ -4645,6 +4749,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
Cocoa/OS X
win
Win32/WGL
+ winvk
+ VK_KHR_win32_surface
angle
Direct3D11 through the OpenGL ES translation layer ANGLE. This supports
almost everything the ``win`` backend does (if the ANGLE build is new
@@ -4653,17 +4759,25 @@ The following video options are currently all specific to ``--vo=opengl`` and
Win32, using WGL for rendering and Direct3D 9Ex for presentation. Works
on Nvidia and AMD. Newer Intel chips with the latest drivers may also
work.
+ d3d11
+ Win32, with native Direct3D 11 rendering.
x11
X11/GLX
+ x11vk
+ VK_KHR_xlib_surface
x11probe
For internal autoprobing, equivalent to ``x11`` otherwise. Don't use
directly, it could be removed without warning as autoprobing is changed.
wayland
Wayland/EGL
+ waylandvk
+ VK_KHR_wayland_surface
drm
- DRM/EGL (``drm-egl`` is a deprecated alias)
+ DRM/EGL
x11egl
X11/EGL
+ android
+ Android/EGL. Requires ``--wid`` be set to an ``android.view.Surface``.
mali-fbdev
Direct fbdev/EGL support on some ARM/MALI devices.
vdpauglx
@@ -4672,50 +4786,67 @@ The following video options are currently all specific to ``--vo=opengl`` and
performance problems), and is for doing experiments only. Will not
be used automatically.
+``--gpu-api=<type>``
+ Controls which type of graphics APIs will be accepted:
+
+ auto
+ Use any available API (default)
+ opengl
+ Allow only OpenGL (requires OpenGL 2.1+ or GLES 2.0+)
+ vulkan
+ Allow only Vulkan (requires a valid/working ``--spirv-compiler``)
+ d3d11
+ Allow only ``--gpu-context=d3d11``
+
``--opengl-es=<mode>``
- Select whether to use GLES:
+ Controls which type of OpenGL context will be accepted:
+ auto
+ Allow all types of OpenGL (default)
yes
- Try to prefer ES over Desktop GL
- force2
- Try to request a ES 2.0 context (the driver might ignore this)
+ Only allow GLES
no
- Try to prefer desktop GL over ES
- auto
- Use the default for each backend (default)
+ Only allow desktop/core GL
-``--opengl-fbo-format=<fmt>``
+``--opengl-restrict=<version>``
+ Restricts all OpenGL versions above a certain version. Versions are encoded
+ in hundreds, i.e. OpenGL 4.5 -> 450. As an example, --opengl-restrict=300
+ would restrict OpenGL 3.0 and higher, effectively only allowing 2.x
+ contexts. Note that this only imposes a limit on context creation APIs, the
+ actual OpenGL context may still have a higher OpenGL version. (Default: 0)
+
+``--fbo-format=<fmt>``
Selects the internal format of textures used for FBOs. The format can
influence performance and quality of the video output. ``fmt`` can be one
of: rgb8, rgb10, rgb10_a2, rgb16, rgb16f, rgb32f, rgba12, rgba16, rgba16f,
- rgba32f. Default: ``auto``, which maps to rgba16 on desktop GL, and rgba16f
- or rgb10_a2 on GLES (e.g. ANGLE), unless GL_EXT_texture_norm16 is
- available.
+ rgba16hf, rgba32f. Default: ``auto``, which maps to rgba16 on desktop GL,
+ and rgba16f or rgb10_a2 on GLES (e.g. ANGLE), unless GL_EXT_texture_norm16
+ is available.
-``--opengl-gamma=<0.1..2.0>``
- Set a gamma value (default: 1.0). If gamma is adjusted in other ways (like
- with the ``--gamma`` option or key bindings and the ``gamma`` property),
- the value is multiplied with the other gamma value.
+``--gamma-factor=<0.1..2.0>``
+ Set an additional raw gamma factor (default: 1.0). If gamma is adjusted in
+ other ways (like with the ``--gamma`` option or key bindings and the
+ ``gamma`` property), the value is multiplied with the other gamma value.
Recommended values based on the environmental brightness:
1.0
- Brightly illuminated (default)
- 0.9
- Slightly dim
- 0.8
- Pitch black room
+ Pitch black or dimly lit room (default)
+ 1.1
+ Moderately lit room, home
+ 1.2
+ Brightly illuminated room, office
- NOTE: Typical movie content (Blu-ray etc.) already contains a gamma drop of
- about 0.8, so specifying it here as well will result in even darker
- image than intended!
+ NOTE: This is based around the assumptions of typical movie content, which
+ contains an implicit end-to-end of about 0.8 from scene to display. For
+ bright environments it can be useful to cancel that out.
``--gamma-auto``
Automatically corrects the gamma value depending on ambient lighting
- conditions (adding a gamma boost for dark rooms).
+ conditions (adding a gamma boost for bright rooms).
- With ambient illuminance of 64lux, mpv will pick the 1.0 gamma value (no
- boost), and slightly increase the boost up until 0.8 for 16lux.
+ With ambient illuminance of 16 lux, mpv will pick the 1.0 gamma value (no
+ boost), and slightly increase the boost up until 1.2 for 256 lux.
NOTE: Only implemented on OS X.
@@ -4856,14 +4987,15 @@ The following video options are currently all specific to ``--vo=opengl`` and
some drivers, so enable at your own risk.
``--tone-mapping-desaturate=<value>``
- Apply desaturation for highlights that exceed this level of brightness. The
- higher the parameter, the more color information will be preserved. This
- setting helps prevent unnaturally blown-out colors for super-highlights, by
- (smoothly) turning into white instead. This makes images feel more natural,
- at the cost of reducing information about out-of-range colors.
+ Apply desaturation for highlights. The parameter essentially controls the
+ steepness of the desaturation curve. The higher the parameter, the more
+ aggressively colors will be desaturated. This setting helps prevent
+ unnaturally blown-out colors for super-highlights, by (smoothly) turning
+ into white instead. This makes images feel more natural, at the cost of
+ reducing information about out-of-range colors.
- The default of 2.0 is somewhat conservative and will mostly just apply to
- skies or directly sunlit surfaces. A setting of 0.0 disables this option.
+ The default of 1.0 provides a good balance that roughly matches the look
+ and feel of the ACES ODT curves. A setting of 0.0 disables this option.
``--gamut-warning``
If enabled, mpv will mark all clipped/out-of-gamut pixels that exceed a
@@ -4871,8 +5003,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
inverted to make them stand out. Note: This option applies after the
effects of all of mpv's color space transformation / tone mapping options,
so it's a good idea to combine this with ``--tone-mapping=clip`` and use
- ``--target-gamut`` to set the gamut to simulate. For example,
- ``--target-gamut=bt.709`` would make mpv highlight all pixels that exceed the
+ ``--target-prim`` to set the gamut to simulate. For example,
+ ``--target-prim=bt.709`` would make mpv highlight all pixels that exceed the
gamut of a standard gamut (sRGB) display. This option also does not work
well with ICC profiles, since the 3DLUTs are always generated against the
source color space and have chromatically-accurate clipping built in.
@@ -4932,7 +5064,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
Blend subtitles directly onto upscaled video frames, before interpolation
and/or color management (default: no). Enabling this causes subtitles to be
affected by ``--icc-profile``, ``--target-prim``, ``--target-trc``,
- ``--interpolation``, ``--opengl-gamma`` and ``--post-shader``. It also
+ ``--interpolation``, ``--gamma-factor`` and ``--glsl-shaders``. It also
increases subtitle performance when using ``--interpolation``.
The downside of enabling this is that it restricts subtitles to the visible
@@ -4962,7 +5094,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
if the video contains alpha information (which is extremely rare). May
not be supported on all platforms. If alpha framebuffers are
unavailable, it silently falls back on a normal framebuffer. Note that
- if you set the ``--opengl-fbo-format`` option to a non-default value, a
+ if you set the ``--fbo-format`` option to a non-default value, a
format with alpha must be specified, or this won't work.
This does not work on X11 with EGL and Mesa (freedesktop bug 67676).
no
@@ -4977,7 +5109,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
Color used to draw parts of the mpv window not covered by video. See
``--osd-color`` option how colors are defined.
-``--opengl-tex-pad-x``, ``--opengl-tex-pad-y``
+``--gpu-tex-pad-x``, ``--gpu-tex-pad-y``
Enlarge the video source textures by this many pixels. For debugging only
(normally textures are sized exactly, but due to hardware decoding interop
we may have to deal with additional padding, which can be tested with these
@@ -4991,8 +5123,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
flipping GL front and backbuffers immediately (i.e. it doesn't call it
in display-sync mode).
-``--opengl-dumb-mode=<yes|no|auto>``
- This mode is extremely restricted, and will disable most extended OpenGL
+``--gpu-dumb-mode=<yes|no|auto>``
+ This mode is extremely restricted, and will disable most extended
features. That includes high quality scalers and custom shaders!
It is intended for hardware that does not support FBOs (including GLES,
@@ -5005,18 +5137,16 @@ The following video options are currently all specific to ``--vo=opengl`` and
This option might be silently removed in the future.
-``--opengl-shader-cache-dir=<dirname>``
- Store and load compiled GL shaders in this directory. Normally, shader
- compilation is very fast, so this is usually not needed. But some GL
- implementations (notably ANGLE, the default on Windows) have relatively
- slow shader compilation, and can cause startup delays.
+``--gpu-shader-cache-dir=<dirname>``
+ Store and load compiled GLSL shaders in this directory. Normally, shader
+ compilation is very fast, so this is usually not needed. It mostly matters
+ for GPU APIs that require internally recompiling shaders to other languages,
+ for example anything based on ANGLE or Vulkan. Enabling this can improve
+ startup performance on these platforms.
NOTE: This is not cleaned automatically, so old, unused cache files may
stick around indefinitely.
- This option might be silently removed in the future, if ANGLE fixes shader
- compilation speed.
-
``--cuda-decode-device=<auto|0..>``
Choose the GPU device used for decoding when using the ``cuda`` hwdec.
@@ -5073,7 +5203,10 @@ Miscellaneous
Media files must use constant framerate. Section-wise VFR might work as well
with some container formats (but not e.g. mkv). If the sync code detects
severe A/V desync, or the framerate cannot be detected, the player
- automatically reverts to ``audio`` mode for some time or permanently.
+ automatically reverts to ``audio`` mode for some time or permanently. These
+ modes also require a vsync blocked presentation mode. For OpenGL, this
+ translates to ``--opengl-swapinterval=1``. For Vulkan, it translates to
+ ``--vulkan-swap-mode=fifo`` (or ``fifo-relaxed``).
The modes with ``desync`` in their names do not attempt to keep audio/video
in sync. They will slowly (or quickly) desync, until e.g. the next seek
@@ -5259,9 +5392,6 @@ Miscellaneous
.. admonition:: Examples
- - ``--lavfi-complex='[aid1] asplit [ao] [t] ; [t] aphasemeter [vo]'``
- Play audio track 1, and visualize it as video using the ``aphasemeter``
- filter.
- ``--lavfi-complex='[aid1] [aid2] amix [ao]'``
Play audio track 1 and 2 at the same time.
- ``--lavfi-complex='[vid1] [vid2] vstack [vo]'``
@@ -5269,13 +5399,11 @@ Miscellaneous
both tracks need to have the same width, or filter initialization
will fail (you can add ``scale`` filters before the ``vstack`` filter
to fix the size).
- - ``--lavfi-complex='[aid1] asplit [ao] [t] ; [t] aphasemeter [t2] ; [vid1] [t2] overlay [vo]'``
- Play audio track 1, and overlay its visualization over video track 1.
- ``--lavfi-complex='[aid1] asplit [t1] [ao] ; [t1] showvolume [t2] ; [vid1] [t2] overlay [vo]'``
Play audio track 1, and overlay the measured volume for each speaker
over video track 1.
- ``null:// --lavfi-complex='life [vo]'``
- Conways' Life Game.
+ A libavfilter source-only filter (Conways' Life Game).
See the FFmpeg libavfilter documentation for details on the available
filters.
diff --git a/DOCS/man/stats.rst b/DOCS/man/stats.rst
new file mode 100644
index 0000000..e06bcd6
--- /dev/null
+++ b/DOCS/man/stats.rst
@@ -0,0 +1,162 @@
+STATS
+=====
+
+This builtin script displays information and statistics for the currently
+played file. It is enabled by default if mpv was compiled with Lua support.
+It can be disabled entirely using the ``--load-stats-overlay=no`` option.
+
+Usage
+-----
+
+The following key bindings are active by default unless something else is
+already bound to them:
+
+==== ==============================================
+i Show stats for a fixed duration
+I Toggle stats (shown until toggled again)
+==== ==============================================
+
+While the stats are visible on screen the following key bindings are active,
+regardless of existing bindings. They allow you to switch between *pages* of
+stats:
+
+==== ==================
+1 Show usual stats
+2 Show frame timings
+==== ==================
+
+Font
+~~~~
+
+For optimal visual experience, a font with support for many font weights and
+monospaced digits is recommended. By default, the open source font
+`Source Sans Pro <https://github.com/adobe-fonts/source-sans-pro>`_ is used.
+
+Configuration
+-------------
+
+This script can be customized through a config file ``lua-settings/stats.conf``
+placed in mpv's user directory and through the ``--script-opts`` command-line
+option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
+
+Configurable Options
+~~~~~~~~~~~~~~~~~~~~
+
+``key_oneshot``
+ Default: i
+``key_toggle``
+ Default: I
+
+ Key bindings to display stats.
+
+``key_page_1``
+ Default: 1
+``key_page_2``
+ Default: 2
+
+ Key bindings for page switching while stats are displayed.
+
+``duration``
+ Default: 4
+
+ How long the stats are shown in seconds (oneshot).
+
+``redraw_delay``
+ Default: 1
+
+ How long it takes to refresh the displayed stats in seconds (toggling).
+
+``persistent_overlay``
+ Default: no
+
+ When `no`, other scripts printing text to the screen can overwrite the
+ displayed stats. When `yes`, displayed stats are persistently shown for the
+ respective duration. This can result in overlapping text when multiple
+ scripts decide to print text at the same time.
+
+``plot_perfdata``
+ Default: yes
+
+ Show graphs for performance data (page 2).
+
+``plot_vsync_ratio``
+ Default: yes
+``plot_vsync_jitter``
+ Default: yes
+
+ Show graphs for vsync and jitter values (page 1). Only when toggled.
+
+``flush_graph_data``
+ Default: yes
+
+ Clear data buffers used for drawing graphs when toggling.
+
+``font``
+ Default: Source Sans Pro
+
+ Font name. Should support as many font weights as possible for optimal
+ visual experience.
+
+``font_mono``
+ Default: Source Sans Pro
+
+ Font name for parts where monospaced characters are necessary to align
+ text. Currently, monospaced digits are sufficient.
+
+``font_size``
+ Default: 8
+
+ Font size used to render text.
+
+``font_color``
+ Default: FFFFFF
+
+ Font color.
+
+``border_size``
+ Default: 0.8
+
+ Size of border drawn around the font.
+
+``border_color``
+ Default: 262626
+
+ Color of drawn border.
+
+``alpha``
+ Default: 11
+
+ Transparency for drawn text.
+
+``plot_bg_border_color``
+ Default: 0000FF
+
+ Border color used for drawing graphs.
+
+``plot_bg_color``
+ Default: 262626
+
+ Background color used for drawing graphs.
+
+``plot_color``
+ Default: FFFFFF
+
+ Color used for drawing graphs.
+
+Note: colors are given as hexadecimal values and use ASS tag order: BBGGRR
+(blue green red).
+
+Different key bindings
+~~~~~~~~~~~~~~~~~~~~~~
+
+A different key binding can be defined with the aforementioned options
+``key_oneshot`` and ``key_toggle`` but also with commands in ``input.conf``,
+for example::
+
+ e script-binding stats/display-stats
+ E script-binding stats/display-stats-toggle
+
+Using ``input.conf``, it is also possible to directly display a certain page::
+
+ i script-binding stats/display-page-1
+ e script-binding stats/display-page-2
diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst
index 617ceee..f43ed87 100644
--- a/DOCS/man/vf.rst
+++ b/DOCS/man/vf.rst
@@ -100,159 +100,6 @@ With filters that support it, you can access parameters by their name.
Available mpv-only filters are:
-``crop[=w:h:x:y]``
- Crops the given part of the image and discards the rest. Useful to remove
- black bands from widescreen videos.
-
- ``<w>,<h>``
- Cropped width and height, defaults to original width and height.
- ``<x>,<y>``
- Position of the cropped picture, defaults to center.
-
-``expand[=w:h:x:y:aspect:round]``
- Expands (not scales) video resolution to the given value and places the
- unscaled original at coordinates x, y.
-
- ``<w>,<h>``
- Expanded width,height (default: original width,height). Negative
- values for w and h are treated as offsets to the original size.
-
- .. admonition:: Example
-
- ``expand=0:-50:0:0``
- Adds a 50 pixel border to the bottom of the picture.
-
- ``<x>,<y>``
- position of original image on the expanded image (default: center)
-
- ``<aspect>``
- Expands to fit an aspect instead of a resolution (default: 0).
-
- .. admonition:: Example
-
- ``expand=800::::4/3``
- Expands to 800x600, unless the source is higher resolution, in
- which case it expands to fill a 4/3 aspect.
-
- ``<round>``
- Rounds up to make both width and height divisible by <r> (default: 1).
-
-``flip``
- Flips the image upside down.
-
-``mirror``
- Mirrors the image on the Y axis.
-
-``rotate[=0|90|180|270]``
- Rotates the image by a multiple of 90 degrees clock-wise.
-
-``scale[=w:h:param:param2:chr-drop:noup:arnd``
- Scales the image with the software scaler (slow) and performs a YUV<->RGB
- color space conversion (see also ``--sws``).
-
- All parameters are optional.
-
- ``<w>:<h>``
- scaled width/height (default: original width/height)
-
- :0: scaled d_width/d_height
- :-1: original width/height
- :-2: Calculate w/h using the other dimension and the prescaled
- aspect ratio.
- :-3: Calculate w/h using the other dimension and the original
- aspect ratio.
- :-(n+8): Like -n above, but rounding the dimension to the closest
- multiple of 16.
-
- ``<param>[:<param2>]`` (see also ``--sws``)
- Set some scaling parameters depending on the type of scaler selected
- with ``--sws``::
-
- --sws=2 (bicubic): B (blurring) and C (ringing)
- 0.00:0.60 default
- 0.00:0.75 VirtualDub's "precise bicubic"
- 0.00:0.50 Catmull-Rom spline
- 0.33:0.33 Mitchell-Netravali spline
- 1.00:0.00 cubic B-spline
-
- --sws=7 (Gaussian): sharpness (0 (soft) - 100 (sharp))
-
- --sws=9 (Lanczos): filter length (1-10)
-
- ``<chr-drop>``
- chroma skipping
-
- :0: Use all available input lines for chroma (default).
- :1: Use only every 2. input line for chroma.
- :2: Use only every 4. input line for chroma.
- :3: Use only every 8. input line for chroma.
-
- ``<noup>``
- Disallow upscaling past the original dimensions.
-
- :0: Allow upscaling (default).
- :1: Disallow upscaling if one dimension exceeds its original value.
- :2: Disallow upscaling if both dimensions exceed their original values.
-
- ``<arnd>``
- Accurate rounding for the vertical scaler, which may be faster or
- slower than the default rounding.
-
- :no: Disable accurate rounding (default).
- :yes: Enable accurate rounding.
-
-``dsize[=w:h:aspect-method:r:aspect]``
- Changes the intended display aspect at an arbitrary point in the
- filter chain. Aspect can be given as a fraction (4/3) or floating point
- number (1.33). Note that this filter does *not* do any scaling itself; it
- just affects what later scalers (software or hardware) will do when
- auto-scaling to the correct aspect.
-
- ``<w>,<h>``
- New aspect ratio given by a display width and height. Unlike older mpv
- versions or MPlayer, this does not set the display size.
-
- Can also be these special values:
-
- :0: original display width and height
- :-1: original video width and height (default)
- :-2: Calculate w/h using the other dimension and the original display
- aspect ratio.
- :-3: Calculate w/h using the other dimension and the original video
- aspect ratio.
-
- .. admonition:: Example
-
- ``dsize=800:-2``
- Specifies a display resolution of 800x600 for a 4/3 aspect
- video, or 800x450 for a 16/9 aspect video.
-
- ``<aspect-method>``
- Modifies width and height according to original aspect ratios.
-
- :-1: Ignore original aspect ratio (default).
- :0: Keep display aspect ratio by using ``<w>`` and ``<h>`` as maximum
- resolution.
- :1: Keep display aspect ratio by using ``<w>`` and ``<h>`` as minimum
- resolution.
- :2: Keep video aspect ratio by using ``<w>`` and ``<h>`` as maximum
- resolution.
- :3: Keep video aspect ratio by using ``<w>`` and ``<h>`` as minimum
- resolution.
-
- .. admonition:: Example
-
- ``dsize=800:600:0``
- Specifies a display resolution of at most 800x600, or smaller,
- in order to keep aspect.
-
- ``<r>``
- Rounds up to make both width and height divisible by ``<r>``
- (default: 1).
-
- ``<aspect>``
- Force an aspect ratio.
-
``format=fmt=<value>:colormatrix=<value>:...``
Restricts the color space for the next filter without doing any conversion.
Use together with the scale filter for a real conversion.
@@ -425,16 +272,6 @@ Available mpv-only filters are:
``<spherical-yaw>``, ``<spherical-pitch>``, ``<spherical-roll>``
Reference angle in degree, if spherical video is used.
-``noformat[=fmt]``
- Restricts the color space for the next filter without doing any conversion.
- Unlike the format filter, this will allow any color space except the one
- you specify.
-
- .. note:: For a list of available formats, see ``noformat=fmt=help``.
-
- ``<fmt>``
- Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
-
``lavfi=graph[:sws-flags[:o=opts]]``
Filter video using FFmpeg's libavfilter.
@@ -449,11 +286,14 @@ Available mpv-only filters are:
If you want to use the full filter syntax with this option, you have
to quote the filter graph in order to prevent mpv's syntax and the
- filter graph syntax from clashing.
+ filter graph syntax from clashing. To prevent a quoting and escaping
+ mess, consider using ``--lavfi-complex`` if you know which video
+ track you want to use from the input file. (There is only one video
+ track for nearly all video files anyway.)
.. admonition:: Examples
- ``-vf lavfi=[gradfun=20:30,vflip]``
+ ``--vf=lavfi=[gradfun=20:30,vflip]``
``gradfun`` filter with nonsense parameters, followed by a
``vflip`` filter. (This demonstrates how libavfilter takes a
graph and not just a single filter.) The filter graph string is
@@ -484,62 +324,6 @@ Available mpv-only filters are:
``'--vf=lavfi=yadif:o="threads=2,thread_type=slice"'``
forces a specific threading configuration.
-``pullup[=jl:jr:jt:jb:sb:mp]``
- Pulldown reversal (inverse telecine) filter, capable of handling mixed
- hard-telecine, 24000/1001 fps progressive, and 30000/1001 fps progressive
- content. The ``pullup`` filter makes use of future context in making its
- decisions. It is stateless in the sense that it does not lock onto a pattern
- to follow, but it instead looks forward to the following fields in order to
- identify matches and rebuild progressive frames.
-
- ``jl``, ``jr``, ``jt``, and ``jb``
- These options set the amount of "junk" to ignore at the left, right,
- top, and bottom of the image, respectively. Left/right are in units of
- 8 pixels, while top/bottom are in units of 2 lines. The default is 8
- pixels on each side.
-
- ``sb`` (strict breaks)
- Setting this option to 1 will reduce the chances of ``pullup``
- generating an occasional mismatched frame, but it may also cause an
- excessive number of frames to be dropped during high motion sequences.
- Conversely, setting it to -1 will make ``pullup`` match fields more
- easily. This may help process video with slight blurring between the
- fields, but may also cause interlaced frames in the output.
-
- ``mp`` (metric plane)
- This option may be set to ``u`` or ``v`` to use a chroma plane instead of the
- luma plane for doing ``pullup``'s computations. This may improve accuracy
- on very clean source material, but more likely will decrease accuracy,
- especially if there is chroma noise (rainbow effect) or any grayscale
- video. The main purpose of setting ``mp`` to a chroma plane is to reduce
- CPU load and make pullup usable in realtime on slow machines.
-
-``yadif=[mode:interlaced-only]``
- Yet another deinterlacing filter
-
- ``<mode>``
- :frame: Output 1 frame for each frame.
- :field: Output 1 frame for each field (default).
- :frame-nospatial: Like ``frame`` but skips spatial interlacing check.
- :field-nospatial: Like ``field`` but skips spatial interlacing check.
-
- ``<interlaced-only>``
- :no: Deinterlace all frames.
- :yes: Only deinterlace frames marked as interlaced (default).
-
- This filter is automatically inserted when using the ``d`` key (or any
- other key that toggles the ``deinterlace`` property or when using the
- ``--deinterlace`` switch), assuming the video output does not have native
- deinterlacing support.
-
- If you just want to set the default mode, put this filter and its options
- into ``--vf-defaults`` instead, and enable deinterlacing with ``d`` or
- ``--deinterlace``.
-
- Also, note that the ``d`` key is stupid enough to insert a deinterlacer twice
- when inserting yadif with ``--vf``, so using the above methods is
- recommended.
-
``sub=[=bottom-margin:top-margin]``
Moves subtitle rendering to an arbitrary point in the filter chain, or force
subtitle rendering in the video filter as opposed to using video output OSD
@@ -558,91 +342,6 @@ Available mpv-only filters are:
subtitle colors and video under the influence of the video equalizer
settings.
-``stereo3d[=in:out]``
- Stereo3d converts between different stereoscopic image formats.
-
- ``<in>``
- Stereoscopic image format of input. Possible values:
-
- ``sbsl`` or ``side_by_side_left_first``
- side by side parallel (left eye left, right eye right)
- ``sbsr`` or ``side_by_side_right_first``
- side by side crosseye (right eye left, left eye right)
- ``abl`` or ``above_below_left_first``
- above-below (left eye above, right eye below)
- ``abr`` or ``above_below_right_first``
- above-below (right eye above, left eye below)
- ``ab2l`` or ``above_below_half_height_left_first``
- above-below with half height resolution (left eye above, right eye
- below)
- ``ab2r`` or ``above_below_half_height_right_first``
- above-below with half height resolution (right eye above, left eye
- below)
-
- ``<out>``
- Stereoscopic image format of output. Possible values are all the input
- formats as well as:
-
- ``arcg`` or ``anaglyph_red_cyan_gray``
- anaglyph red/cyan gray (red filter on left eye, cyan filter on
- right eye)
- ``arch`` or ``anaglyph_red_cyan_half_color``
- anaglyph red/cyan half colored (red filter on left eye, cyan filter
- on right eye)
- ``arcc`` or ``anaglyph_red_cyan_color``
- anaglyph red/cyan color (red filter on left eye, cyan filter on
- right eye)
- ``arcd`` or ``anaglyph_red_cyan_dubois``
- anaglyph red/cyan color optimized with the least-squares
- projection of Dubois (red filter on left eye, cyan filter on right
- eye)
- ``agmg`` or ``anaglyph_green_magenta_gray``
- anaglyph green/magenta gray (green filter on left eye, magenta
- filter on right eye)
- ``agmh`` or ``anaglyph_green_magenta_half_color``
- anaglyph green/magenta half colored (green filter on left eye,
- magenta filter on right eye)
- ``agmc`` or ``anaglyph_green_magenta_color``
- anaglyph green/magenta colored (green filter on left eye, magenta
- filter on right eye)
- ``aybg`` or ``anaglyph_yellow_blue_gray``
- anaglyph yellow/blue gray (yellow filter on left eye, blue filter
- on right eye)
- ``aybh`` or ``anaglyph_yellow_blue_half_color``
- anaglyph yellow/blue half colored (yellow filter on left eye, blue
- filter on right eye)
- ``aybc`` or ``anaglyph_yellow_blue_color``
- anaglyph yellow/blue colored (yellow filter on left eye, blue
- filter on right eye)
- ``irl`` or ``interleave_rows_left_first``
- Interleaved rows (left eye has top row, right eye starts on next
- row)
- ``irr`` or ``interleave_rows_right_first``
- Interleaved rows (right eye has top row, left eye starts on next
- row)
- ``ml`` or ``mono_left``
- mono output (left eye only)
- ``mr`` or ``mono_right``
- mono output (right eye only)
-
-``gradfun[=strength[:radius|:size=<size>]]``
- Fix the banding artifacts that are sometimes introduced into nearly flat
- regions by truncation to 8-bit color depth. Interpolates the gradients that
- should go where the bands are, and dithers them.
-
- ``<strength>``
- Maximum amount by which the filter will change any one pixel. Also the
- threshold for detecting nearly flat regions (default: 1.5).
-
- ``<radius>``
- Neighborhood to fit the gradient to. Larger radius makes for smoother
- gradients, but also prevents the filter from modifying pixels near
- detailed regions (default: disabled).
-
- ``<size>``
- size of the filter in percent of the image diagonal size. This is
- used to calculate the final radius size (default: 1).
-
``vapoursynth=file:buffered-frames:concurrent-frames``
Loads a VapourSynth filter script. This is intended for streamed
processing: mpv actually provides a source filter, instead of using a
@@ -857,9 +556,3 @@ Available mpv-only filters are:
which algorithm is actually selected. ``none`` always falls back. On
most if not all hardware, this option will probably do nothing, because
a video processor usually supports all modes or none.
-
-``buffer=<num>``
- Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
- useless, except for debugging. (Note that this won't help to smooth out
- latencies with decoding, because the filter will never output a frame if
- the buffer isn't full, except on EOF.)
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 1552b21..f4ebb64 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -14,7 +14,7 @@ in the list.
See ``--vo=help`` for a list of compiled-in video output drivers.
- The recommended output driver is ``--vo=opengl``, which is the default. All
+ The recommended output driver is ``--vo=gpu``, which is the default. All
other drivers are for compatibility or special purposes. If the default
does not work, it will fallback to other drivers (in the same order as
listed by ``--vo=help``).
@@ -273,37 +273,34 @@ Available video output drivers are:
``--vo-direct3d-exact-backbuffer``
Always resize the backbuffer to window size.
-``opengl``
- OpenGL video output driver. It supports extended scaling methods, dithering
- and color management.
+``gpu``
+ General purpose, customizable, GPU-accelerated video output driver. It
+ supports extended scaling methods, dithering, color management, custom
+ shaders, HDR, and more.
- See `OpenGL renderer options`_ for options specific to this VO.
+ See `GPU renderer options`_ for options specific to this VO.
By default, it tries to use fast and fail-safe settings. Use the
- ``opengl-hq`` profile to use this driver with defaults set to high
- quality rendering. (This profile is also the replacement for
- ``--vo=opengl-hq``.) The profile can be applied with ``--profile=opengl-hq``
- and its contents can be viewed with ``--show-profile=opengl-hq``.
+ ``gpu-hq`` profile to use this driver with defaults set to high quality
+ rendering. The profile can be applied with ``--profile=gpu-hq`` and its
+ contents can be viewed with ``--show-profile=gpu-hq``.
- Requires at least OpenGL 2.1.
-
- Some features are available with OpenGL 3 capable graphics drivers only
- (or if the necessary extensions are available).
-
- OpenGL ES 2.0 and 3.0 are supported as well.
+ This VO abstracts over several possible graphics APIs and windowing
+ contexts, which can be influenced using the ``--gpu-api`` and
+ ``--gpu-context`` options.
Hardware decoding over OpenGL-interop is supported to some degree. Note
that in this mode, some corner case might not be gracefully handled, and
color space conversion and chroma upsampling is generally in the hand of
the hardware decoder APIs.
- ``opengl`` makes use of FBOs by default. Sometimes you can achieve better
- quality or performance by changing the ``--opengl-fbo-format`` option to
+ ``gpu`` makes use of FBOs by default. Sometimes you can achieve better
+ quality or performance by changing the ``--gpu-fbo-format`` option to
``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include Mesa/Intel not
accepting ``rgb16``, Mesa sometimes not being compiled with float texture
support, and some OS X setups being very slow with ``rgb16`` but fast
with ``rgb32f``. If you have problems, you can also try enabling the
- ``--opengl-dumb-mode=yes`` option.
+ ``--gpu-dumb-mode=yes`` option.
``sdl``
SDL 2.0+ Render video output driver, depending on system with or without
@@ -507,3 +504,19 @@ Available video output drivers are:
Mode ID to use (resolution, bit depth and frame rate).
(default: 0)
+ ``--drm-overlay=<number>``
+ Select the DRM overlay index to use.
+ Overlay index is zero based, and related to crtc.
+ (default: 0)
+
+``mediacodec_embed`` (Android)
+ Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
+ Requires ``--hwdec=mediacodec`` for hardware decoding, along with
+ ``--vo=mediacodec_embed`` and ``--wid=(intptr_t)(*android.view.Surface)``.
+
+ Since this video output driver uses native decoding and rendering routines,
+ many of mpv's features (subtitle rendering, OSD/OSC, video filters, etc)
+ are not available with this driver.
+
+ To use hardware decoding with ``--vo-gpu`` instead, use
+ ``--hwdec=mediacodec-copy`` along with ``--gpu-context=android``.
diff --git a/DOCS/mplayer-changes.rst b/DOCS/mplayer-changes.rst
index 7c8ec50..66cacb3 100644
--- a/DOCS/mplayer-changes.rst
+++ b/DOCS/mplayer-changes.rst
@@ -76,8 +76,8 @@ Video
* Wayland support.
* Native support for VAAPI and VDA. Improved VDPAU video output.
-* Improved OpenGL output (see the ``opengl-hq`` video output).
-* Make hardware decoding work with the ``opengl`` video output.
+* Improved GPU-accelerated video output (see the ``gpu-hq`` preset).
+* Make hardware decoding work with the ``gpu`` video output.
* Support for libavfilter (for video->video and audio->audio). This allows
using most of FFmpeg's filters, which improve greatly on the old MPlayer
filters in features, performance, and correctness.
@@ -85,7 +85,7 @@ Video
for BT.2020 (Ultra HD). linear XYZ (Digital Cinema) and SMPTE ST2084 (HDR)
inputs.
* Support for color managed displays, via ICC profiles.
-* High-quality image resamplers (see the ``opengl`` ``scale`` suboption).
+* High-quality image resamplers (see the ``--scale`` suboption).
* Support for scaling in (sigmoidized) linear light.
* Better subtitle rendering using libass by default.
* Improvements when playing multiple files (``-fixed-vo`` is default, do not
diff --git a/LICENSE b/LICENSE.GPL
index 7b845ee..7b845ee 100644
--- a/LICENSE
+++ b/LICENSE.GPL
diff --git a/LICENSE.LGPL b/LICENSE.LGPL
new file mode 100644
index 0000000..e5ab03e
--- /dev/null
+++ b/LICENSE.LGPL
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library 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.
+
+ This library 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/README.md b/README.md
index ce38279..16ac975 100644
--- a/README.md
+++ b/README.md
@@ -5,20 +5,29 @@
--------------
+* [External links](#external-links)
* [Overview](#overview)
+* [System requirements](#system-requirements)
* [Downloads](#downloads)
* [Changelog](#changelog)
* [Compilation](#compilation)
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
+* [FFmpeg ABI compatibility](#ffmpeg-abi-compatibility)
* [Release cycle](#release-cycle)
* [Bug reports](#bug-reports)
* [Contributing](#contributing)
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
+* [License](#license)
+* [Contact](#contact)
+
+
+## External links
+
+
* [Wiki](https://github.com/mpv-player/mpv/wiki)
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
-* [Man pages](http://mpv.io/manual/master/)
-* [Contact](#contact)
-* [License](#license)
+* [Manual](http://mpv.io/manual/master/)
+
## Overview
@@ -30,10 +39,10 @@ Releases can be found on the [release list][releases].
## System requirements
-- A not too ancient Linux, or Windows Vista or later, or OSX 10.8 or later.
-- A somewhat capable CPU. Hardware decoding might sometimes help if the CPU
- is too slow to decode video realtime, but must be explicitly enabled with
- the `--hwdec` option.
+- A not too ancient Linux, Windows 7 or later, or OSX 10.8 or later.
+- A somewhat capable CPU. Hardware decoding might help if the CPU is too slow to
+ decode video in realtime, but must be explicitly enabled with the `--hwdec`
+ option.
- A not too crappy GPU. mpv is not intended to be used with bad GPUs. There are
many caveats with drivers or system compositors causing tearing, stutter,
etc. On Windows, you might want to make sure the graphics drivers are
@@ -45,7 +54,7 @@ Releases can be found on the [release list][releases].
For semi-official builds and third-party packages please see
-[mpv.io](http://mpv.io/installation/).
+[mpv.io/installation](http://mpv.io/installation/).
## Changelog
@@ -67,9 +76,9 @@ Changes to the default key bindings are indicated in
Compiling with full features requires development files for several
external libraries. Below is a list of some important requirements.
-The mpv build system uses *waf*, but we don't store it in your source tree. The
-script './bootstrap.py' will download the latest version of waf that was tested
-with the build system.
+The mpv build system uses [waf](https://waf.io/), but we don't store it in the
+repository. The `./bootstrap.py` script will download the latest version
+of waf that was tested with the build system.
For a list of the available build options use `./waf configure --help`. If
you think you have support for some feature installed but configure fails to
@@ -101,9 +110,6 @@ Essential dependencies (incomplete list):
- Audio output development headers (libasound/ALSA, pulseaudio)
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
and either libswresample or libavresample)
- At least FFmpeg 3.2.2 or Libav 12 is required.
- For hardware decoding with vaapi and vdpau, FFmpeg 3.3 or Libav git is
- required.
- zlib
- iconv (normally provided by the system libc)
- libass (OSD, OSC, text subtitles)
@@ -111,7 +117,6 @@ Essential dependencies (incomplete list):
- libjpeg (optional, used for screenshots only)
- uchardet (optional, for subtitle charset detection)
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
- (FFmpeg 3.3 or Libav git is also required.)
Libass dependencies:
@@ -130,11 +135,10 @@ FFmpeg dependencies:
- Libav also works, but some features will not work. (See section below.)
Most of the above libraries are available in suitable versions on normal
-Linux distributions. However, FFmpeg is an exception (distro versions may be
-too old to work well or at all). For that reason you may want to use
-the separately available build wrapper ([mpv-build][mpv-build]) that first
-compiles FFmpeg libraries and libass, and then compiles the player statically
-linked against those.
+Linux distributions. For ease of compiling the latest git master of everything,
+you may wish to use the separately available build wrapper ([mpv-build][mpv-build])
+which first compiles FFmpeg libraries and libass, and then compiles the player
+statically linked against those.
If you want to build a Windows binary, you either have to use MSYS2 and MinGW,
or cross-compile from Linux with MinGW. See
@@ -149,20 +153,6 @@ of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
with FFmpeg only (subtitle formats in particular).
-## Preferred FFmpeg version
-
-
-Using the latest FFmpeg release (or FFmpeg git master) is strongly recommended.
-Older versions are unsupported, even if the build system still happens to
-accept them. The main reason mpv still builds with older FFmpeg versions is to
-evade arguing with people (users, distros) who insist on using older FFmpeg
-versions for no rational reason.
-
-If you want to use a stable FFmpeg release, use the latest release, which has
-most likely the best maintenance out of all stable releases. Older releases
-are for distros, and at best receive basic changes, like fixing critical security
-issues or build fixes, and at worst are completely abandoned.
-
## FFmpeg ABI compatibility
mpv does not support linking against FFmpeg versions it was not built with, even
@@ -203,6 +193,8 @@ recommended (see [Contact](#Contact) below).
## Contributing
+Please read [contribute.md][contribute.md].
+
For small changes you can just send us pull requests through GitHub. For bigger
changes come and talk to us on IRC before you start working on them. It will
make code review easier for both parties later on.
@@ -221,6 +213,12 @@ For details see [FAQ entry](https://github.com/mpv-player/mpv/wiki/FAQ#How_is_mp
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
and largely unmaintained list of changes is located [here][mplayer-changes].
+## License
+
+GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
+See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
+
+
## Contact
@@ -244,8 +242,4 @@ only if discretion is required.
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
-
-## License
-
-
-Mostly GPLv2 or later. See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
+[contribute.md]: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 1bf4f15..f19f548 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,116 +1,18 @@
-Release 0.27.0
+Release 0.28.0
==============
-Features
---------
-
-Added
-~~~~~
-
-- Windows: handle media keys
-- libmpv: options: add a thread-safe way to notify option updates
-- vd_lavc/vo_opengl: support embedded ICC profiles
-- vo: rendering API abstraction for future non-GL video outputs
-- vo_opengl: add a gamut warning feature to highlight out-of-gamut colors (--gamut-warning)
-- vo_opengl: add direct rendering support (--vd-lavc-dr)
-- vo_opengl: implement (faster) compute shader based EWA kernel
-- vo_opengl: implement HLG OOTF inverse
-- vo_opengl: support HDR peak detection (--hdr-compute-peak)
-- vo_opengl: support float input pixel formats
-- vo_opengl: support loading custom user textures (#4586)
-- vo_opengl: support user compute shaders
-
-
-Removed
-~~~~~~~
-
-- Remove video equalizer handling from vo_direct3d, vo_sdl, vo_vaapi, and vo_xv (GPL, not worth the effort to support legacy VOs)
-
-
-Options and Commands
---------------------
+This release needs recent FFmpeg (newer than 3.4) due to major refactoring.
+Required library versions:
-Added
-~~~~~
-
-- macOS: add --no-native-fs option to disable the native macOS fullscreen (#4014)
-- player: add --track-auto-selection option
-
-
-Changed
-~~~~~~~
-
-- input: use mnemonic names for mouse buttons, same as Qt: https://doc.qt.io/qt-5/qt.html#MouseButton-enum
-- options: change --loop semantics
-- player: make --lavfi-complex changeable at runtime
-- vf_eq: remove this filter (GPL; uses libavfilter’s eq filter now, with changed semantics)
-- video: change --deinterlace behavior
-- vo_opengl: generalize HDR tone mapping to gamut mapping, --hdr-tone-mapping → --tone-mapping
-
-
-Removed
-~~~~~~~
-
-- --field-dominance (GPL-only author, no chance of relicensing)
-- input: drop deprecated "osd" command
-- options: drop --video-aspect-method=hybrid (GPL-only)
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- TOOLS/autocrop.lua: fix cropdetect black limit for 10-bit videos
-- TOOLS/lua/autodeint: update to lavfi-bridge
-- TOOLS/lua/status-line: improve and update
-- af_lavrresample: don't call swr_set_compensation() unless necessary (#4716)
-- ao_oss: fix period_size calculation (#4642)
-- ao_rsound: allow setting the host
-- audio: fix spdif mode
-- filter_kernels: correct spline64 kernel
-- macOS: fix media keys input when other Apps steal the priority (#4834)
-- macOS: fix the support of multiple renderers (GPU switch) (#2371)
-- macOS: remove the standard "Show Tab Bar" menu item
-- options: fix --include (#4673)
-- player: fix --end with large values (#4650)
-- player: fix confusion in audio resync code (#4688)
-- player: make refresh seeks slightly more robust (#4757)
-- player: readd smi subtitle extension (#4626)
-- vd_lavc: change auto-probe order to prefer cuda over vdpau-copy
-- vd_lavc: fix device leak with copy-mode hwaccels (#4735)
-- vd_lavc: fix hwdec compatibility with yuvj420p formats
-- vd_lavc: fix mid-stream hwdec fallback
-- vf_vapoursynth: fix inverted sign and restore 10 bit support (#4720)
-- video: increase --monitorpixelaspect range
-- vo_opengl: adjust the rules for linearization (#4631)
-- vo_opengl: scale deband-grain to the signal range
-- vo_opengl: tone map on the maximum signal component
-- x11: fix that window could be resized when using embedding (#4784)
-- ytdl_hook: resolve relative paths when joining segment urls (#4827)
-- ytdl_hook: support fragments with relative paths, fixes segmented DASH
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.26.0..v0.27.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.26.0...v0.27.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.26.0..v0.27.0
-
-
-Release 0.26.0
-==============
-
-VA-API/VDPAU hardware decoding now requires FFmpeg > 3.2.
-DVB support is disabled by default since it does not work with some older kernels.
-Built-in V4L TV support is disabled by default. av://v4l2 can be used instead.
-Support for C plugins is now enabled by default (#4491).
-
-Many more parts of the player are now licensed under LGPL, see Copyright file.
-Thanks to all contributors who have agreed to relicensing of their changes!
-See #2033 for details.
+ * libavutil >= 56.6.100
+ * libavcodec >= 58.7.100
+ * libavformat >= 58.0.102
+ * libswscale >= 5.0.101
+ * libavfilter >= 7.0.101
+ * libswresample >= 3.0.100
+The LGPL 2.1+ relicensing process is now mostly complete. The remaining GPL-only
+code (see Copyright file) can be disabled at build time by using --enable-lgpl.
Features
--------
@@ -118,309 +20,33 @@ Features
Added
~~~~~
-- Universal Windows Plaform (UWP) support (libmpv only)
-- csputils: implement sony s-gamut
-- js: add javascript scripting support using MuJS
- See DOCS/man/javascript.rst for more details
-- vo_opengl: add new HDR tone mapping algorithm (mobius, now default)
-- vo_opengl: hwdec_cuda: Support separate decode and display devices
-- vo_opengl: implement sony s-log1 and s-log2 trc
-- vo_opengl: implement support for OOTFs and non-display referred content
+- Add DRM_PRIME Format Handling and Display for RockChip MPP decoders
+- csputils: Add support for Display P3 primaries
+- demux: support multiple seekable cached ranges, display cache ranges on OSC
+- demux_playlist: support .url files (#5107)
+- dvb: Add multiple frontends support (up to 8)
+- dvb: implement parsing of modulation for VDR-style channels config
+- hwdec: add mediacodec hardware decoder for IMGFMT_MEDIACODEC frames,
+ rename mediacodec to mediacodec-copy
+- lua: integrate stats.lua script (bound to i/I by default)
+- vd_lavc: add support for nvdec hwaccel
+- vo_gpu: add android opengl backend
+- vo_gpu: initial d3d11 support
+- vo_gpu: vulkan support
Removed
~~~~~~~
-- vf_dlopen: remove this filter
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- vo_opengl: add --tone-mapping-desaturate
-- vo_opengl: support tone-mapping-param for `clip`
-- ytdl_hook: add option to exclude URLs from being parsed
-
-
-Changed
-~~~~~~~
-
-- allow setting profile option with libmpv
-- audio: move replaygain control to top-level options
-- external_files: parse ~ in --{sub,audio}-paths
-- options: change --sub-fix-timing default to no (#4484)
-- options: expose string list actions for --sub-file option
-- options: slight cleanup of --sub-ass-style-override
- * signfs → scale
- * --sub-ass-style-override → --sub-ass-override
-- renamed the HDR TRCs `st2084` and `std-b67` to `pq` and `hlg` respectively
-- replace vf_format's `peak` suboption by `sig-peak`, which is relative to the reference white level instead of in cd/m^2
-- the following options change to append-by-default (and possibly separator):
- * --script
-- video: change --video-aspect-method default value to `container`
-
-
-Deprecated
-~~~~~~~~~~
-
-- m_option: deprecate multiple items for -add etc.
-- player: deprecate "osd" command
-- --audio-file-paths => --audio-file-path
-- --sub-paths => --sub-file-path
-- --opengl-shaders => --opengl-shader
-- --sub-paths => --sub-file-paths
-- the following options are deprecated for setting via API:
- * "script" (use "scripts")
- * "sub-file" (use "sub-files")
- * "audio-file" (use "audio-files")
- * "external-file" (use "external-files")
- (the compatibility hacks for this will be removed after this release)
-
-
-Removed
-~~~~~~~
-
-- chmap: remove misleading "downmix" channel layout name (#4545)
-- demux_lavf: remove --demuxer-lavf-cryptokey option (#4579)
-- input.conf: drop TV/DVB bindings
-- options: remove remaining deprecated audio device selection options
- * --alsa-device
- * --oss-device
- * --coreaudio-exclusive
- * --pulse-sink
- * --rsound-host/--rsound-port
- * --ao-sndio-device
- * --ao-wasapi-exclusive
- * --ao-wasapi-device
-- remove option --target-brightness
-- remove property "video-params/nom-peak"
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- TOOLS/lua/autoload.lua: actually sort files case insensitive (#4398)
-- TOOLS/lua/autoload.lua: ignores all files starting with "."
-- ao_openal: kill off device listing (#4311)
-- ao_pulse: reorder format choice to prefer float and S32 over S16 as fallback format
-- ao_wasapi: try correct initial format (#4582)
-- command: add missing change notification for playlist-shuffle (#4573)
-- demux_disc: fix bluray subtitle language retrieval (#4611)
-- demux_mkv: fix alpha with vp9 + libvpx
-- demux_mkv: support FFmpeg A_MS/ACM extensions
-- ipc-unix: don’t truncate the message on EAGAIN (#4452)
-- ipc: raise json nesting limit (#4394)
-- macOS: fix mpv-wrapper.sh when used with csh or tcsh shell
-- macOS: properly unhide Dock when quitting during System fs animation
-- mpv_identify: replace deprecated fps property (#4550)
-- options/path: fallback to USERPROFILE if HOME isn't set
-- player: close audio device on no audio track
-- player: fix potential segfault when playing dvd:// with DVD disabled (#4393)
-- player: prevent seek position to jump around adjacent keyframes, e.g. when dragging the OSC bar on short videos (#4183)
-- vo_opengl: bump up SHADER_MAX_HOOKS and MAX_TEXTURE_HOOKS to 64
-- vo_opengl: correct off-by-one in scale=oversample
-- vo_opengl: do not use vaapi-over-GLX (#4555)
-- vo_opengl: fall back to ordered dither instead of blowing up (#4519)
-- vo_opengl: tone map in linear XYZ instead of RGB
-- x11: add 128x128 sized icon support
-- ytdl_hook: add a header to support geo-bypass
-- ytdl_hook: don't override start time set by saved state
-- ytdl_hook: don't override user-set start time
-- ytdl_hook: treat single-entry playlists as a single video
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.25.0..v0.26.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.25.0...v0.26.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.25.0..v0.26.0
-
-
-Release 0.25.0
-==============
-
-This release drops support for OS X 10.7 and earlier.
-Support for some optical media functionality (DVD/CD) is now disabled by default.
-
-More parts of the player are now licensed under LGPL. In particular:
-
- * OSD symbol font
- * ass_mp, sd_ass
- * common.h
- * demux/packet
- * demux_mkv (with minor exceptions), ebml, matroska.h
- * sd_lavc
- * sub/osd
-
-Thanks to all contributors who have agreed to relicensing of their changes!
-See #2033 for details.
-
-Starting with this release, releases will be tagged on the master branch.
-The release/current branch is thus abandoned.
-
-
-Features
---------
-
-Added
-~~~~~
-
-- TOOLS: add acompressor.lua script for runtime acompressor ffmpeg filter control
-- dvb: add support for DVB-T2
-- lavfi: support hwdec filters for --lavfi-complex
-- macOS: initial Touch Bar support
-- osc: add volume button (mouse wheel to change volume)
-- sub: add SDH subtitle filter
-- vo_opengl: add experimental vdpauglx backend
-- vo_opengl: implement videotoolbox hwdec on iOS
-
-
-Removed
-~~~~~~~
-
-- image_writer: remove useless formats (PPM, PGM, TGA)
-- af_drc: remove (use --af=acompressor instead; higher quality)
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- --demuxer-lavf-probe-info
-- --sub-filter-sdh, --sub-filter-sdh-harder
-- add automatic libavfilter bridges to option parsing
-- command: add better runtime filter toggling method
-- command: add demux-start-time property
-- command: add property notifications for hwdec properties (#4289)
-- input: add "async" flag
-- osc: add user_opts.boxmaxchars for box layout title limit
-- player: add --keep-open-pause=no option
-- va_vavpp: reversal-bug=no
-- vo_opengl: --opengl-es=force2
-- vo_opengl: add --opengl-shader-cache-dir option for caching shaders on disk (mostly for ANGLE)
-- vo_opengl: angle: add --angle-flip to set the ANGLE present model
-
-
-Changed
-~~~~~~~
-
-- command: update sub-fps etc. options on runtime changes
-- vo_opengl: prefer X11 backends over Wayland backends
-- options: change --audio-file-auto default to not to load any files
-
-
-Deprecated
-~~~~~~~~~~
-
-- audio: deprecate most non-lavfi audio filters
- (channels, equalizer, pan, volume)
-- video: deprecate almost all non-lavfi video filters
- (buffer, crop, dlopen, dsize, eq, expand, flip, gradfun, mirror, noformat,
- pullup, rotate, scale, stereo3d, sub, yadif)
-- options: deprecate --loop semantics (planned alias to --loop-file)
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- Windows: demux_cue: fix UTF-8 paths
-- Windows: fix mismatched free/talloc_free (#4315)
-- Windows: fix undefined behaviour when toggling fullscreen
-- ao_alsa: close lost audio devices (#4189)
-- ao_alsa: filter fewer devices
-- ao_alsa: fix an error check (#4188)
-- ao_jack: update latency on buffer_size/graph change
-- ao_wasapi: do not pass nonsense to drivers with double precision formats
-- ass_mp: reallocate cached subtitle image data on format changes (#4325)
-- build: decouple dvdnav check from dvdread (#4290)
-- build: encode_lavc: fix build failure after libavcodec major bump
-- build: fix build with HAVE_GL==0
-- build: replace android-gl check with a standard GLES3 check
-- build: update bundled waf to 1.9.8
-- build: vd_lavc: fix potential build failure with vaapi
-- demux_lavf: disable half-working mp4 edit list support in libavcodec
-- demux_lavf: skip avformat_find_stream_info() for some formats (hls, mp4, mkv by default)
-- etc/encoding_profiles.conf: update and remove deprecated stuff
-- etc/mpv.conf: remove deprecated options
-- external_files: actually try to autoload from fallback paths
-- external_files: enable autoloading with URLs (#3264)
-- image_writer: make it work with libavcodec's jpg encoder
-- macOS: add --ontop-level option for modifying ontop window level (#2376, #3974)
-- macOS: add key mappings for previous and next Media Keys (#4204)
-- macOS: add option to force dedicated GPU (#3242)
-- macOS: fix autofit options on HiDPI resolutions without HiDPI scaling (#4194)
-- macOS: fix black edges on live resize
-- macOS: fix cursor hiding in the Dock area and top of the screen
-- macOS: fix event propagation of menu bar item key shortcuts
-- macOS: fix first responder when borderless window is used
-- macOS: fix key input in certain circumstances
-- macOS: fix retrieval of non-fullscreen window size, also while animating (#4323)
-- macOS: fix scroll wheel input with Shift modifier (#3506)
-- macOS: improve bundle environment variable handling (#2061)
-- macOS: make window draggable on init
-- macOS: only move window into screen bounds when changing screens (#4178)
-- macOS: only report mouse movements when window is not being dragged (might have triggered OSC and other scripts)
-- macOS: properly restore shell state when quitting from the Dock's context menu
-- macOS: refactor mouse events and cursor visibility (#1817, #3856, #4147)
-- macOS: set background of the title bar from black to white
-- osc: bottom/topbar: add fullscreen button
-- osc: bottom/topbar: don't clip title vertically
-- osc: bottom/topbar: increase timecodes width a bit (#3952)
-- osc: fix PlayResX undefined warning when aspect is 0
-- osc: fix window dragging with showwindowed=no (#1819)
-- osc: make title configurable and use property expansion on it (#4221)
-- osc: refactor osc message scaling (#4081, #4083, #4102)
-- player: don't block playback stop when seeking
-- player: enable "buffering" pausing for DASH streams too
-- player: fix core-idle and eof-reached update notifcations
-- player: make screenshot commands honor the async flag (#4250)
-- player: reduce blocking on VO when switching pause (#4152)
-- sd_ass: disable --sub-fix-timing if sub style override is fully disabled
-- stream/stream_dvdnav: don't ignore setting title (#4283)
-- stream_dvd: fix subs/audio detection on DVDs containing multi-PGC titles
-- vf_vavpp: add advanced deint bug compatibility for Intel vaapi drivers
-- vf_vavpp: fix first-field mode
-- vo_opengl: fix crash with temporal dithering in dumb mode
-- vo_opengl: hwdec_d3d11egl: make it work with some ANGLE DLL versions
-- vo_x11: reduce flickering on playlist navigation
-- wayland: correctly map mouse buttons
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.24.0..v0.25.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.24.0...v0.25.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.24.0..v0.25.0
-
-
-Release 0.24.0
-==============
-
-Features
---------
-
-Added
-~~~~~
-
-- Windows: allow snapping to screen edges (#2248)
-- macOS: add border cycling (#2430)
-- player: add experimental C plugin interface
-- player: add experimental stream recording feature (--record-file)
-- player: add prefetching of the next playlist entry (--prefetch-playlist; does NOT work with URLs resolved by youtube-dl)
-- stream_lavf: add support for data URIs (#4058)
-- vaapi: add support for 10-bit video formats (requires patched Mesa and capable hardware)
-- x11: pseudo HiDPI scaling
+- af: remove deprecated audio filters (channels, equalizer, pan, volume;
+ replacements in lavfi)
+- vf: remove most GPL video filters (crop, dsize, expand, flip, gradfun, mirror,
+ noformat, pullup, rotate, scale, stereo3d, yadif; replacements in lavfi)
+- vf_buffer: remove this filter
+- video: remove automatic stereo3d filter insertion
+- vo_gpu: remove hwdec_vaglx interop
+- vo_opengl: refactor into vo_gpu
+- vo_wayland: remove
Options and Commands
@@ -429,2417 +55,106 @@ Options and Commands
Added
~~~~~
-- sub: add --sub-justify and --sub-ass-justify options
-- sub: add option to force using video resolution for image subtitles (--image-subs-video-resolution)
+- demux: add option to create CC tracks eagerly (--sub-create-cc-track)
+- options: add --start=none to reset previously set start time
+- options: add --vlang switch
Changed
~~~~~~~
-- command: rename framedrop properties
- - drop-frame-count → decoder-frame-drop-count
- - vo-drop-frame-count → frame-drop-count
-- input.conf: change "L" to change loop-file by default
-
-
-Deprecated
-~~~~~~~~~~
-
-- --hwdec-preload (replaced with --opengl-hwdec-interop)
+- cache: lower default size to 2*10MB
+- demux: bump the demuxer cache readahead duration to 10 hours
+- demux: use seekable cache for network by default, bump prefetch limit
+- msg: make --msg-level affect --log-file too
+- player/misc.c: allow both --length and --end to control play endpoint
+- player: match subtitles with language tags with --sub-auto=exact
+- rename --opengl-hwdec-interop to --gpu-hwdec-interop (now mostly useless)
+- vd_lavc: prefer nvdec over vdpau with --hwdec=auto (better codec and surface
+ format support)
+- vd_lavc: rename --hwdec=rpi to --hwdec=mmal
Removed
~~~~~~~
-- options: drop deprecated --sub-codepage syntax
-- options: drop deprecated --vd/--ad codecs selection features
-- player: remove --stream-capture option/property
+- options: remove --heartbeat-cmd and --heartbeat-interval
+ (incidentally fixes #4888)
Fixes and Minor Enhancements
----------------------------
-- Windows: update the fullscreen state on restoring
-- ad_spdif: fix obscure cases of AC3 passthrough (e.g. 44100 Hz AC3)
-- ao_oss: use --audio-device if --oss-device isn't set (#4122)
-- build: fix --disable-gl if cuda is enabled
-- build: rpi: rely on pkgconfig for compiler flags to ease cross compilation
-- charset_conv: fallback to interpreting subs as latin1 if iconv fails
-- charset_conv: fix "auto" fallback with uchardet not compiled (#3954)
-- client API: fix freeze when destroying mpv_handle before mpv_initialize
-- client API: fix mpv_set_property() return value before init (#3988)
-- command: fix potential crash for script-binding with multi-commands
-- command: nicer OSD formatting for loop-file
-- command: shorten long playlists on OSD
-- config: do not resolve default profile during "include" processing (#4024)
-- cuda: fix 10 bit decoding
-- cue: accept lower-case cue commands (#4057)
-- demux_mkv: any reference makes a frame not a keyframe (fixes seeking in WebM files with alpha channel)
-- lavfi: slightly better disconnected output handling (#4118)
-- lua: close directory after reading its entries (#4045)
-- macOS: consistent normalization when searching for external files (#4016)
-- macOS: don't change Space on quit in fullscreen (#3957)
-- macOS: don't constrain window frame for fullscreen (#4044)
-- macOS: don't init displaylink on reconfig (#4031)
-- macOS: fix build on OS X 10.9 (#3946)
-- macOS: fix color profile retrieval
-- macOS: fix displaylink refresh rate retrieval
-- macOS: fix dropping of files and URLs (#4036)
-- macOS: fix handling of geometry option (#3867)
-- macOS: fix unwanted behavior with window level other than the default (#1757, #1884)
-- macOS: fix window size in certain circumstances
-- macOS: properly recover from toggleFullscreen fail (#4035)
-- osc: allow playlist buttons when looping (#4092)
-- osc: bottom/topbar: clip title instead of stretching
-- osc: bottombar/topbar: make chapter markers slightly bigger
-- osc: box: clip with ellipsis after too much stretching
-- osc: fix crashes related to field eventresponder being nil (#3210)
-- player: add .scc subtitle extension
-- stream_bluray: use proper 0-based index
-- sub: remove .txt as text subtitle extension
-- tv: fix segfaults on TV input (#4096)
-- vaapi: set libva > 0.39.4 message callbacks to prevent it from printing messages to the terminal
-- vd_lavc: improved fallback behavior for --hwdec=cuda (#3914)
-- vdpau: reject decoding of non-4:2:0 (would decode to garbage)
-- vf_lavfi: don't crash with VOs without hardware decoding support (#4064)
-- vo_opengl, vo_opengl_cb: better hwdec interop backend selection
-- vo_opengl: angle: rewrite with custom swap chain (fixes problems with e.g. 24 fps video on 24 Hz monitors, avoids problems with DirectComposition, enables "screenshot window" command on Windows 8 and newer)
-- vo_opengl: partially fix rotation for 4:2:2 content
-- vo_opengl: x11egl: fix alpha mode
-- wayland: fix high CPU usage with mpv paused and visible
-- ytdl_hook, edl: implement pseudo-DASH support
-- ytdl_hook: add non-dash fallbacks to default formats
-- ytdl_hook: fix opening hitbox.tv rtmp stream
-- ytdl_hook: respect --no-audio, don't force-select track
-- ytdl_hook: support livestream segmented DASH VODs
-- ytdl_hook: support segmented dash
+- TOOLS/autoload.lua: add ogm, ogg and opus extensions
+- Use /dev/tty instead of stdin for terminal input (#4190)
+- audio: add audio softvol processing to AO (replaces previously GPL’d code)
+- audio: fix channel conversion with NA channels (e.g. with ALSA)
+- audio: fix missing volume update on init and reinit
+- csputils: Fix DCI P3 primaries white point
+- demux: don't allow subtitles to mess up buffered time display
+- demux: fix .cue files with audio files that contain attached pictures
+- demux: fix accounting for seekable ranges on track switches (fixes missing
+ audio when cycling through audio tracks with e.g. EDL, --merge-files, ordered
+ chapters and youtube-dl pseudo DASH)
+- demux: fix crash with cue/ordered chapter files (#5027)
+- demux: speed up cache seeking with a coarse index
+- demux_lavf: always give libavformat the filename when probing
+ (helps with mp3 files)
+- demux_mkv: add V_SNOW tag to mkv_video_tags
+- dvb: Fix long channel switching: next/prev channel
+- dvb: fixes for ATSC tuning
+- lavc_conv: clamp timestamps to positive (#5047)
+- macOS: fix bundle on macOS High Sierra (10.13) (#4926, #4866)
+- mp_image: always copy color attributes on hw download (#4804)
+- mp_image: select an explicit fallback for chroma location (#4804)
+- msg: bump log level of --log-file to -v -v
+- msg: reinterpret a bunch of message levels
+- osc: fix rare stack overflow when changing visibility mode
+- osdep/io: add android-related bullshit to fix files >2 GiB
+- player/playloop.c: respect playback start time when using --loop-file
+- player: allow seeking in cached parts of unseekable streams
+- player: make track language matching case insensitive (#5272)
+- player: use start timestamp for ab-looping if --ab-loop-a is absent
+- player: when loading external file, always add all track types (#5132)
+- restore-old-bindings.conf: add old macOS/Wayland AXIS bindings
+- screenshot: create directories from template
+- scripting: report dlerror() output
+- sd_ass: accept RFC8081 font media types
+- sd_ass: accept otc as fallback OpenType collection file extension
+- stream_libarchive: work around various types of locale braindeath
+ (https://git.io/vbiFJ)
+- subprocess-win: don't change the mouse cursor when creating processes
+- video: add a hack to avoid missing subtitles with vf_sub (#5194)
+- video: fix alpha handling (#4983)
+- video: fix memory leaks (roughly 1 KB per decoded frame) with hwdec copy modes
+- video: fix rotation and deinterlace auto filters
+- video: properly pass through ICC data
+- vo: add support for externally driven renderloop and make wayland use it
+ (partially fixes display-sync under wayland; disables rendering when window
+ is invisible)
+- vo_gpu: change --tone-mapping-desaturate algorithm
+- vo_gpu: enable 3DLUTs in dumb mode
+- vo_gpu: fix gamma scale
+- vo_gpu: fix mobius tone mapping compatibility to GLSL 120 (#5069)
+- vo_gpu: fix video sometimes not being rerendered on equalizer change
+- vo_gpu: kill off FBOTEX_FUZZY (#1814)
+- vo_gpu: opengl: fix possible screenshot window crash (#4905)
+- vo_gpu: opengl: use GLX_MESA_swap_control where available
+- vo_gpu: reduce the --alpha=blend-tiles checkerboard intensity
+- vo_gpu: win: remove exclusive-fullscreen detection hack
+- vo_lavc: remove messy delayed subtitle rendering logic (#4689)
+- wayland_common: implement output tracking, many cleanups and bugfixes
+- Windows: skip window snapping if Windows handled it
+- Windows: add more-POSIXy versions of open() and fstat() (#4711)
+- ytdl_hook: don't prepend ytdl:// to non-youtube links in playlists (#5003)
This listing is not complete. Check DOCS/client-api-changes.rst for a history
of changes to the client API, and DOCS/interface-changes.rst for a history
of changes to other user-visible interfaces.
-A complete changelog can be seen by running `git log v0.23.0..v0.24.0`
+A complete changelog can be seen by running `git log v0.27.0..v0.28.0`
in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.23.0...v0.24.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.23.0..v0.24.0
-
-
-Release 0.23.0
-==============
-
-Now requires at least FFmpeg 3.2.2.
-
-Features
---------
-
-- vo_rpi: partially undeprecate
-
-Added
-~~~~~
-
-- vo_opengl: hwdec_cuda: Support P016 output surfaces
-
-Removed
-~~~~~~~
-
-- charset_conv: drop enca and libguess support in favor of uchardet
-- vf_vdpaurb: remove this filter in favor of --hwdec=vdpau-copy
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- TOOLS/autoload: allow disabling through script-opts
-- demux, stream: add --access-references to prevent opening referenced files
-
-
-Deprecated
-~~~~~~~~~~
-
-- options: deprecate codec family selection in --vd/--ad
-
-
-Removed
-~~~~~~~
-
-- macOS: remove --fs-black-out-screens
-- options: remove deprecated sub-option handling for --vo and --ao
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- Windows: window styles improvements (allow minimizing borderless/fullscreen window) (#2229, #2451)
-- ad_spdif: Fix crash when spdif muxer is not available
-- audio: fix --audio-stream-silence with ao_alsa
-- audio: fix --audio-stream-silence with ao_wasapi
-- build: drop build-time dependency on Perl
-- build: support linking ANGLE (previously loaded dynamically)
-- d3d11va: unconditionally load D3D DLLs (#3348)
-- demux_mkv: fix seeking in some broken files (#3920)
-- hwdec_cuda: allow building without CUDA SDK (load CUDA dynamically)
-- macOS: fix dropping of URLs containing query strings on the window
-- macOS: fullscreen refactoring (#2857, #3272, #1352, #2062, #3864)
-- macOS: support append file to paylist on drop (#2166)
-- macOS: update the menu and remove conflicting “Quit & remember position” item (#3865)
-- osc: don't hide playlist buttons, just disable
-- osc: fix possible race condition in right timecode
-- osc: topbar: use same styles as bottombar
-- player: don't print format detection error when aborting loading
-- vdpau: fix vaapi probing if libvdpau-va-gl1 is present
-- video: use demuxer-signaled duration for last video frame (#3924)
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.22.0..v0.23.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.22.0...v0.23.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.22.0..v0.23.0
-
-
-Release 0.22.0
-==============
-
-Features
---------
-
-Added
-~~~~~
-
-- audio/out: add AudioUnit output driver for iOS
-- demux_mkv: parse Matroska colorimetry metadata
-- filter_kernels: add ability to taper kernels/windows, add tukey window
-- osc: add seekbarstyle=knob (#2365)
-- video/out: add tct as modern caca alternative for true-color and 256-color terminals
-- video: add --hwdec=vdpau-copy mode
-
-
-Deprecated
-~~~~~~~~~~
-
-- vf_vdpaurb: deprecated in favor of --hwdec=vdpau-copy
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- --opengl-early-flush=auto
-- --scale-taper, --scale-wtaper
-- --scale-wblur
-- macOS: --hidpi-window-scale (#3716)
-- osc: add script message handlers for chapter/track/playlists
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- Apply --autofit-larger after --autofit-smaller (#3753)
-- Set subtitle track title to indicate hearing/visual impaired tracks
-- ao_alsa: disable chmap API use for mono/stereo (#2905, #3045)
-- build: add required failure message for libavfilter check (#3692)
-- build: fix compilation with mingw-w64/Clang (#3800)
-- build: make VideoToolbox available on iOS
-- command: fix reset-on-next-file=all and tv-freq option (#3708)
-- command: if window-scale can't be set properly, set it as option (#3724)
-- demux_mkv: don't recursively resolve timeline for opened reference files
-- demux_mkv: fix ordered chapter sources with ordered editions
-- opengl: compile against iOS OpenGLES implementation
-- options: handle legacy no-* sub-options
-- osc: add alpha animation to tooltip (fix lingering tooltip)
-- osc: change default deadzonesize to 0.5
-- osc: don't wrap the title
-- osc: fix crash after reaching a certain position in limited lists (#3691)
-- osc: fix crash with no chapters
-- osc: fix crashes when dragging seekbar across file changes (#3210)
-- osc: fix displaying only half of the entries when at the end of a list
-- osc: fix missing chapter ticks with seekbarstyle=bar
-- osc: slimbox: fix clipping with seekbarstyle=bar (#3737)
-- osc: top/bottombar: also scale when min-width is reached to match box/slimbox behavior
-- osc: top/bottombar: dynamically size timecodes according to timems
-- osc: top/bottombar: rescale layout to same size with scale=1
-- osc: top/bottombar: scale title if too large like box
-- player: consistently initialize screensaver state with --force-window
-- player: enable no-video subtitle display on coverart too
-- player: make --start-time work with --rebase-start-time=no
-- player: make sure non-video subtitle rendering is reset if video resumes (#3770)
-- player: removing last playlist entry while looping should not stop (#3808)
-- player: show subtitles on VO if --force-window is used (#3684)
-- player: speed up audio/video re-sync when there is a huge delay
-- vdpau: fix hwdec uninit (#3788)
-- vo_opengl: blend against background color for --alpha=blend
-- vo_opengl: context_rpi: fix stdatomic usage (#3699)
-- vo_opengl: fix --blend-subtitles handling (#3773)
-- vo_opengl: fix redrawing with hardware decoding (#3773)
-- vo_opengl: partially re-enable glFlush() calls (#3670)
-- ytdl_hook: sort chapters by time
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.21.0..v0.22.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.21.0...v0.22.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.21.0..v0.22.0
-
-
-Release 0.21.0
-==============
-
-Features
---------
-
-Added
-~~~~~
-
-- config: allow profile forward-references in default profile
-- demux_lavf: "support" mov edit lists and log errors if used (FFmpeg only)
-- hwdec: Add support for CUDA and cuvid/NvDecode (mostly useful on Linux where VDPAU still lacks HEVC Main 10 support; keep using DXVA2 on Windows)
-- osc: add right-click behavior to playlist and chapter buttons
-- osc: add user-alterable margin for top/bottombar
-- rpi: add --hwdec=rpi-copy (#3604)
-- sd_lavc: enable teletext
-- vaapi: support drm devices when running in vaapi-copy mode
-- vd_lavc: Add hwdec wrapper for crystalhd
-- vo_opengl: add hw overlay support and use it for RPI
-- vo_opengl: basic mali fbdev support
-- vo_opengl: rpi: merge vo_rpi features
-
-
-Removed
-~~~~~~~
-
-- vo_opengl: remove pre/post/scale-shaders in favor of user-shaders
-
-
-Deprecated
-~~~~~~~~~~
-
-- config: deprecate ao and vo auto-profiles
-- vo_rpi: deprecate this VO
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- add --teletext-page option
-- af_pan: add af-command support to change the matrix
-- af_rubberband: add af-command and option to change the pitch
-- command: add a load-script command
-- command: add a video-dec-params property
-- command: add an apply-profile command
-- command: add audio-pts property to get the audio pts
-- command: add options to property list
-- command: add sub-text property for current subtitle text
-- command: export profile list as a property (#977)
-- options: add --hwdec=yes as alias for --hwdec=auto
-- player: add --player-operation-mode=pseudo-gui
-- player: add --video-osd=no option to disable video OSD (#3387)
-- player: add --watch-later-directory option
-- stream_bluray: select title by playlist (bd://mpls/[playlist])
-- vo_opengl: disable glFlush() by default, and add an option to enable it for testing
-
-
-Changed
-~~~~~~~
-
-- af_rubberband: default to channels=together
-- command: allow absolute seeks relative to end of stream
-- command: make bitrate properties observable
-- command: make most options observable
-- msg: make --log-file and --dump-stats accept config path expansion (#3591)
-- options: deprecate --playlist-pos to --playlist-start
-- options: make input options generally runtime-settable
-- options: rename subtitle options (--sub-text- → --sub; --ass- → --sub-ass-)
-- osc: change default layout to bottombar, seekbarstyle to bar, use larger scalewindowed and scalefullscreen
-- player: make --terminal, --log-file, --dump-stats, --osc, --ytdl, --audio-*, --priority, --stop-screensaver (#3615) settable at runtime
-- vo_drm: change CLI options
- - Change connector selection to accept human readable names (such as eDP-1, HDMI-A-2) rather than arbitrary numbers.
- - Change GPU selection to accept GPU number rather than device paths.
- - Merge connector and GPU selection into one --drm-connector.
- - Add support for --drm-connector=help.
- - Add support for --drm-* in EGL backend.
-- vo_opengl: rename 3dlut-size to icc-3dlut-size
-
-
-Removed
-~~~~~~~
-
-- command: remove hwdec-active and hwdec-detected properties
-- command: remove vo-cmdline
-- options: drop unreferenced --bluray-angle option
-- vo, ao: disable positional parameter suboptions
-- vo_xv: replace no-colorkey with ck-method=none
-
-
-Deprecated
-~~~~~~~~~~
-
-- all VOs and AOs: deprecate sub-options, add them as global options
-- command: deprecate "cache" property, replace with "cache-percent"
-- deprecate --vo-defaults
-- deprecate --vo=opengl-hq alias in favor of opengl-hq profile
-- options: deprecate --mute=auto
-- vo_direct3d: deprecate direct3d_shaders alias, use shaders by default
-- vo_opengl: deprecate 'drm-egl' backend and introduce 'drm' instead
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- TOOLS/zsh.pl: die if we can't parse main options
-- TOOLS/zsh.pl: don't filter files by extension (#2273)
-- ao_alsa: try to fallback to "hdmi" before "iec958" for spdif
-- ao_rsound: fix compilation (#3501)
-- aspect: use nominal width instead of actual width for video-unscaled
-- audio/out: prevent underruns with spdif under certain conditions
-- audio: fix late audio start (#3610)
-- audio: fix missed wakeup when changing audio output device
-- audio: fix segfault when yanking USB DAC
-- charset_conv: Use CP949 instead of EUC-KR
-- command: don't log "ignore" command with -v verbosity
-- command: try selecting the next track if track switching fails (#3446)
-- demux_mkv: don't crash if --ordered-chapters-files fails
-- displayconfig: treat a refresh rate of 1 as invalid
-- ipc: log when listening to IPC socket (#3598)
-- macOS: fix fullscreen regression on 10.11 and newer (#3364)
-- mp_image: fix clearing to black with p010 format
-- osc: Fix scaling issues when toggling fullscreen (#3429)
-- osc: align text vertically in top/bottombar (#2093)
-- osc: change seekbar background's alpha scaling
-- osc: fix display of chapters and playlist scaling
-- osc: move tooltip to inside seekbar for top/bottombar
-- osc: show playlist/chapter list on prev/next instead of osd
-- path: default ~~ paths to home directory (#3591)
-- player: do not let pseudo-gui override user config settings
-- player: enable reading from stdin after loading input.conf
-- player: fix instant subtitle refresh on track switches
-- player: make --force-window work with opengl-cb
-- stream_file: don't use poll() on directories (would hang on macOS; #3530, #3649)
-- stream_lavf: fix determining seekability (#1701)
-- sub: actually apply text alignment options to non-ASS subtitles
-- terminal-win: support modifier keys in console input (#3625)
-- vo_drm: fix segfault when using invalid card
-- vo_opengl: apply 90° rotation to chroma texture size (#3568)
-- vo_opengl: fix incorrect video rendering after vdpau preemption recovery
-- vo_opengl: partially fix dumb-mode cropping with rotation
-- vo_opengl: rpi: use overlay for yuv420p too
-- w32_common: initialize playback status as soon as possible (#3482)
-- wayland: reject resize events with either dimension being 0 (#3679)
-- x11: fix external fullscreen update (#3570)
-- ytdl_hook: Add title to playlist items if available
-- ytdl_hook: Set aspect ratio for anamorphic video
-- ytdl_hook: Support playlist entries without subtitles
-- ytdl_hook: add chapters by parsing video's description
-- ytdl_hook: don't add subtitles with unknown duration
-- ytdl_hook: temporarily force disable dash segments formats
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.20.0..v0.21.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.20.0...v0.21.0 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.20.0..v0.21.0
-
-
-Release 0.20.0
-==============
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- aspect: add --video-unscaled=downscale-big
-- player: add --image-display-duration option to control duration of image display (#3425)
-- vo_opengl: angle: new flag (dcomposition) to control DirectComposition
-- command: add sub-speed property
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- af_lavrresample: fix error if resampler could not be recreated
-- audio: avoid missed wakeups with ab-loops
-- audio: do not apply --audio-channels if spdif passthrough is in use (#3445)
-- cache: don't use a backbuffer if the cache is as large as the file
-- command: prevent O(n^2) behaviour for playlist property
-- demux: close underlying stream if it's fully read anyway (#3456)
-- demux: fix undefined behavior with ogg metadata update (#3451)
-- player: make looping slightly more seamless
-- player: refresh very low framerate video on filter changes (#3435)
-- stream_memory: disable stream cache
-- vf_rotate: allow arbitrary rotation (#3434)
-- vo: be more trusting to estimated display FPS (#3433)
-- w32_common: use hooks to detect parent window resize
-- x11: work around mutter fullscreen issue (#2072)
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.19.0..v0.20.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.19.0...v0.20.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.19.0..v0.20.0
-
-
-Release 0.19.0
-==============
-
-Build System Changes
---------------------
-
-- build: add --htmldir option
-- build: always require atomics
-- wscript: add proper unversioned SONAME for Android
-
-
-Features
---------
-
-New
-~~~
-
-- client API: add stream_cb API for user-defined stream implementations (bumps client API version to 1.22)
-- vf_d3d11vpp: add video processor selection
-- videotoolbox: add --hwdec=videotoolbox-copy for h/w accelerated decoding with video filters
-- vo_opengl: add a tscale=linear direct implementation
-
-Removed
-~~~~~~~
-
-- audio/filter: remove delay audio filter
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- command: add filename/no-ext sub-property that returns filename without extension (#3404)
-- command: add properties for HDR metadata
-- command: add replaygain information properties to track-list
-- options: add vp9 to --hwdec-codecs
-- player: add --audio-stream-silence
-- player: add --audio-wait-open
-- player: add --no-autoload-files
-- videotoolbox: add yuv420p to --videotoolbox-format
-
-Changed
-~~~~~~~
-
-- options: un-restrict --audio-delay
-- use - as command-name separator everywhere
-- vo_opengl: reduce default 3dlut-size to 64x64x64 (since accuracy is improved)
-
-
-Deprecated
-~~~~~~~~~~
-
-- deprecate "balance" option/property (no replacement)
-
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- Windows: don't wait for GUI thread when polling for events (#3393)
-- af_lavcac3enc: error out properly if encoding fails
-- af_volume: don't let softvol overwrite af_volume volumedb sub-option
-- ao_pulse: fix some volume control rounding issues
-- ao_wasapi: in exclusive mode, do not output multichannel by default
-- audio: add heuristic to move auto-downmixing before other filters
-- audio: show an osd bar when changing ao-volume
-- demux: make ALBUM replaygain tags optional (#3405)
-- demux_raw: fix small typo to add s16be support
-- demux_timeline: restore mkv edition switching
-- libarchive: sanitize non-UTF8 archive entries
-- macOS/vo_opengl: fix crash when glctx is NULL during init (#3360)
-- player: disable display-sync with spdif transcoding
-- player: do not cut off terminal status line if it contains newlines (#3340)
-- player: fix display-sync timing if audio resumes slowly
-- player: improve instant track switching (#3392)
-- player: improve non-hr seeking with external audio tracks
-- player: offset demuxer on start/seek properly with audio/sub delay
-- player: sync audio as well when enabling it mid-stream
-- stream/stream_bluray: display list of available titles in verbose mode
-- sub: don't potentially discard too many subtitles on seek
-- video: respect --deinterlace=auto
-- vo_direct3d: add missing header (fixes Cygwin build)
-- vo_opengl: angle: try D3D9 when D3D11 fails eglInitialize
-- vo_opengl: angle: use WARP if there are no hw adapters (makes it work on Windows 7 without hardware-accelerated graphics)
-- vo_opengl: increase 3DLUT accuracy at smaller LUT sizes
-- vo_opengl: remove the 3dlut-size npot2 restriction
-- vo_wayland: fix high CPU usage due to busy polling
-- wayland_common: clip window size to the display output size
-- wayland_common: fix crashes when switching to fullscreen before the video output is fully initialized
-- wayland_common: fix fullscreen image switching bug
-- wayland_common: prevent black bars on most non-native aspect ratios
-- wayland_common: remove untested/unusable wayland dnd code
-- win32: mpv.rc: re-add version info
-- x11: skip ICC update on every window move
-- ytdl: Error out with http_dash_segments (unsupported for now)
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.18.1..v0.19.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.18.1...v0.19.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.1..v0.19.0
-
-
-Release 0.18.1
-==============
-
-Note: Running mpv with different versions of the FFmpeg/Libav libraries than
-it was compiled with is no longer supported. Even supposedly ABI-compatible
-versions have been a source of trouble, and it creates far too much
-complexity with little to no benefit, coupled with absurd and unusable FFmpeg
-API artifacts.
-
-Instead, mpv will exit with an error when such a situation is detected.
-This simply means that mpv needs to be rebuilt whenever FFmpeg libraries change.
-
-
-Features
---------
-
-New
-~~~
-
-- d3d: implement screenshots for --hwdec=d3d11va
-- vo_opengl: add output_size uniform to custom shader
-- vo_opengl: implement the Panasonic V-Log function (#3157)
-- vo_opengl: implement ARIB STD-B68 (HLG) HDR TRC
-
-
-Options and Commands
---------------------
-
-Changed
-~~~~~~~
-- command: pack sub image data in overlay-add command
-
-
-Deprecated
-~~~~~~~~~~
-
-- options: deprecate --heartbeat-cmd
-- audio: deprecate --softvol
-
-
-Removed
-~~~~~~~
-
-- audio: drop --softvol=no and --softvol=auto (#3322)
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- video: fix deinterlace filter handling on pixel format changes
-- x11: silence xdg-screensaver
-- vo_opengl: angle: update the swapchain on resize (#3301)
-- vo_opengl: error out gracefully when trying to use FBOs without FBO API
-- vd_lavc: expose mastering display side data reference peak (improves results with HDR content)
-- vo_opengl: generalize HDR tone mapping mechanism (#3293)
-- vo_opengl: don't constantly resize the output FBO
-- vo_opengl: use ringbuffer of PBOs
-- Windows: make WM_NCHITTEST simpler and more accurate
-- ao_oss: do not add an entry to audio-device-list if device file missing
-- dec_audio: fix segment boudnary switching
-- ao_lavc, vo_lavc: Migrate to new FFmpeg encoding API
-- vo_opengl: explicitly use main framebuffer when reading window content (#3284)
-- vo_xv: fix behavior with odd sizes
-- audio: insert auto-inserted filters at end of chain
-- x11: add missing FocusChangeMask (disables key repeat when losing focus while a key is down)
-- ao_coreaudio: error out when selecting invalid device
-- ad_lavc: work around misbehavior of some FFmpeg decoders like wmapro (#3297)
-- player: cut off status line on terminal width
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.18.0..v0.18.1`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.18.0...v0.18.1 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.0..v0.18.1
-
-
-Release 0.18.0
-==============
-
-
-Build System Changes
---------------------
-
-- build: Do not link to libGL for egl-drm
-- build: also use the iconv check on FreeBSD
-- build: don't install tests, only build them
-- build: re-enable encoding mode by default
-- vo_opengl: hwdec: remove build-dependency on dxva2 (#3150)
-- wscript: make at least 1 OpenGL output mandatory
-
-
-Features
---------
-
-New
-~~~
-
-- csputils: add SMPTE ST2084 support
-- demux_mkv: support Matroska webvtt (#3247)
-- demux_playlist: read directories recursively
-- stream_memory: add hex:// protocol
-- vf_crop: support opaque hardware decoding formats
-- vf_d3d11vpp: add a D3D11 video processor filter
-- vo_opengl: D3D11VA + ANGLE interop
-- vo_opengl: add an angle-es2 backend
-- vo_opengl: angle: dynamically load ANGLE
-- vo_opengl: d3d11egl: native NV12 sampling support
-- vo_opengl: enable color management on GLES
-- vo_opengl: implement HDR (SMPTE ST2084)
-- vo_opengl: implement tone mapping algorithms
-- vo_opengl: make PBOs work on GLES 3.x
-- vo_opengl: support external user hooks, enhancing the flexibility of user shaders
-- vo_opengl: vdpau interop without RGB conversion
-- wayland: implement HIDPI support
-
-
-Removed
-~~~~~~~
-
-- vo_opengl: remove nnedi3 prescaler (replaced by user shaders)
-- vo_opengl: remove prescaling framework with superxbr prescaler (replaced by user shaders)
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- Windows: make taskbar progress indication optional (#2535)
-- af_lavcac3enc: make encoder configurable
-- command: add playlist-pos-1 property (#2828)
-- command: introduce hwdec-current and hwdec-interop properties.
-- options: add --fit-border video option (currently Windows only)
-- video: add --hwdec=auto-copy mode
-- vo_opengl: always autoselect ANGLE as backend if available
-- vo_opengl: expose performance timers as properties
-- x11: add --x11-bypass-compositor=never
-- x11: extend --x11-bypass-compositor with fs-only option (#2582)
-
-
-Changed
-~~~~~~~
-
-- command: allow setting panscan etc. properties if no video is active
-- command: don't seek immediately when setting a-b loop while paused
-- command: if only ab-loop-b is set, loop from start of file
-- options: --geometry: center window position after applying size (#2397)
-- player: loop on end of file if ab-loop-b is unset
-- sd_add: replace --sub-ass=no with --ass-style-override=strip
-
-
-Removed
-~~~~~~~
-
-- vo_opengl: remove non-working rgb/rgba FBO formats
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- TOOLS/zsh.pl: add .f4v extension in zsh completions
-- TOOLS/zsh.pl: complete --audio-device
-- Windows: center window on original window center on resize to fit screen
-- Windows: fix size calculations for window resize (#2935)
-- Windows: fix wrong behavior with window-scale when window size exceeds screen size
-- Windows: make VOCTRL_SET_UNFS_WINDOW_SIZE resize the window around its center (#3164)
-- af_lavcac3enc: fix custom bitrates
-- ao_alsa: add more workarounds for hardware with broken drivers (e.g. ODROID-C2)
-- ao_opensles: remove 32-bit audio formats (not supported by Android)
-- cocoa: fix actual display refresh rate retrieval
-- cocoa: use displaylink without manually tracking the display id (#2392)
-- command: improve playlist* properties change notifications (#3267)
-- command: slightly nicer OSD list formatting
-- compatibility with recent FFmpeg APIs
-- d3d: fix hardware decoding of most MPEG2 things
-- d3dva: move Intel_H264_NoFGT_ClearVideo to lower priority (#3059)
-- demux_mkv: better resync behavior for broken google-created webms
-- demux_mkv: fix seeking with files that miss the first index entry
-- demux_playlist: recognize m3u8 as playlist extension (#3154)
-- input: fix parsing multiple input command prefixes
-- lcms: don't warn/error on 3dlut cache misses
-- lcms: improve black point handling (especially BT.1886)
-- macOS: handle multiple dropped files on the window (#3076)
-- player: always show the first frame in DS mode
-- player: assume video forwards timestamps jumps only with some formats (#3027)
-- player: do not update OSD all the time when paused
-- player: eagerly redraw OSD when seeking with coverart
-- player: fix use-after-free with --screenshot-directory (#3049)
-- player: force VO reconfig when unselecting video track
-- player: really start audio only once video is ready
-- sd_lavc: work around bug in older FFmpeg releases (#3109)
-- stream_cdda: enable cache by default
-- sub: fix --sub-gauss
-- vd_lavc: better hwdec wrapper decoder selection
-- vo_opengl: EGL: fix hwdec probing
-- vo_opengl: angle: avoid fullscreen FBO copy for flipping
-- vo_opengl: angle: enable DirectComposition (lowers vsync jitter)
-- vo_opengl: angle: prevent DXGI hooking Alt+Enter
-- vo_opengl: avoid outputting ultra-wide-gamut by default
-- vo_opengl: correctly disable interpolation if tscale can't be used
-- vo_opengl: fix bicubic_fast in ES mode
-- vo_opengl: fix d3d11 hardware decoding probing on Windows 7
-- vo_opengl: improve scale=oversample performance
-- vo_opengl: make the screen blue on shader errors
-- vo_opengl: partially fix 0bgr format support
-- vo_opengl: possibly update icc profile after changing options
-- vo_opengl: request core profile on X11/EGL too
-- vo_opengl: require at least ES 3.0 for float textures
-- vo_opengl: vdpau: fix certain cases of preemption recovery failures
-- vo_rpi: attempt to survive display mode changes
-- vo_rpi: fix destroying overlays (#3100)
-- vo_rpi: wait for vsync with a timeout
-- vo_sdl: fix pixel formats.
-- vo_xv: Handle incorrect size returned by Xv(Shm)CreateImage (#320)
-- wayland: correctly report display refresh rate
-- wayland: use the advertised size in fullscreen (#3021, #2657)
-- x11: tell GNOME to use dark window decorations
-- ytdl_hook: fix brightcove urls
-- ytdl_hook: just check if protocol is rtmp (#3090)
-- ytdl_hook: support multi-arc subtitles
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.17.0..v0.18.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.17.0...v0.18.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.17.0..v0.18.0
-
-
-Release 0.17.0
-==============
-
-Note: The client API examples have moved to https://github.com/mpv-player/mpv-examples
-
-Build System Changes
---------------------
-
-- install symbolic SVG icon
-- build: allow plain-gl build on OSX (#2980)
-- build: disable encoding mode by default (uses deprecated FFmpeg APIs)
-
-
-Features
---------
-
-New
-~~~
-
-- csputils: add DCI-P3 colorspace
-- d3d11va hwdec
-- demux: add null demuxer
-- ipc: add Windows implementation with named pipes
-- mediacodec decoder hwdec wrapper
-- vo_opengl: add dxva2 interop to angle backend
-- vo_opengl: generate 3DLUT against source and use full BT.1886 (#2815)
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- command: add cache-speed property
-- command: add keepaspect property
-- command: add video-stereo-mode property (#2994)
-- command: export canonical ffmpeg version identifier (ffmpeg-version)
-- command: export lists of all codecs (decoder-list and encoder-list)
-- osd: add italic font for osd (#3031)
-
-
-Changed
-~~~~~~~
-
-- aspect: make video-zoom logarithmic (#3004)
-- command: export more information under track-list
-- ipc: rename --input-unix-socket to --input-ipc-server
-- vo_opengl: decrease default superxbr-edge-strength
-- vo_opengl: rename prescale to prescale-luma
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- TOOLS/zsh.pl: don't complete URLs by default unless no files match (#2892)
-- ad_lavc, vd_lavc: support new Libav decoding API
-- cache: disable useless "Cache is not responding" warning (#3019)
-- demux: delay bitrate calculation on packets with unknown timestamps (#2903)
-- demux_timeline: set correct seekable flags (#2898)
-- input: accept plain text for drag and drop (#2945)
-- input: do not force double-click emulation for artificial commands (#2899)
-- lavc_conv: fix Libav srt subtitles (#2888)
-- player: add missing audio reconfig events (#2929, #2920)
-- player: add wv to list of external audio file extensions
-- player: fix --stream-dump exit code (#2848)
-- player: fix breakage when combining 3D and rotate auto-filters
-- playlist: improve shuffle algorithm (better uniformity)
-- sub: interpret "text" subtitles as srt
-- vo_opengl, osd: allow osc.lua to react faster on resizes
-- vo_opengl: GLX: try to create 3.3 core profile context (#2938)
-- vo_opengl: draw transparency checkerboard after upscaling
-- vo_opengl: fix operation without GL_ARB_texture_rg
-- vo_opengl: improve superxbr algorithm
-- vo_opengl: only open one OpenGL/DX interop handle when using dxva2 (fixes interop with AMD drivers)
-- wayland: don't set fs mode on every configure (#2817)
-- x11: do not set _NET_WM_BYPASS_COMPOSITOR by default (#2997)
-- ytdl_hook: handle optional format_note
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API, and DOCS/interface-changes.rst for a history
-of changes to other user-visible interfaces.
-
-A complete changelog can be seen by running `git log v0.16.0..v0.17.0`
-in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.16.0...v0.17.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.16.0..v0.17.0
-
-
-
-Release 0.16.0
-==============
-
-This release changes the license of some non-MPlayer source files to LGPL 2.1 or later.
-
-
-Build System Changes
---------------------
-
-- build: enable vaapi under drm-only as well (issue #2808)
-- build: enable vo_opengl_cb if GL headers are present
-- build: make libavfilter mandatory
-- build: make posix_spawn optional
-- wscript: don’t install the encoding profiles with encoding disabled
-
-
-Features
---------
-
-New
-~~~
-
-- Initial Android support
-- ao: initial OpenSL ES support
-- dxva2: support HEVC Main 10
-- osc: add always-on mode and unify visibility mode (always/never/auto)
-- player: add complex filter graph support
-- rpi: add mpeg-4, vc-1 decoding support
-- stream_dvb: support frontends with multiple delivery systems (e.g. DVB-C/DVB-T combo cards)
-- vo_opengl: 10 bit support with ANGLE
-- vo_opengl: add KMS/DRM VAAPI hardware decoding interop
-- vo_opengl: dxinterop: add dxva2 passthrough
-- vo_rpi: add geometry handling (--geometry, --autofit, fullscreen switching, etc.)
-- vo_x11: add 16bpp support
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- --lavfi-complex option for complex filter graphs
-- audio: change downmix behavior, add --audio-normalize-downmix
-- command: add vf-command and af-command commands
-- player: add --external-file option
-- vo_opengl: add interpolation-threshold sub-option
-
-
-Changed
-~~~~~~~
-
-- audio: change --audio-channels default back to stereo
-- audio: remove default preference for libdcadec (decoder was merged with FFmpeg)
-- command: always allow setting volume/mute properties
-- command: show original aspect in video-aspect property too
-- input: ignore --input-cursor for events injected by input commands (issue #2750)
-- options: set fs=yes by default on RPI, and change RPI defaults handling
-- sub: implement "sub-seek 0" (issue #2791)
-- vo_opengl: default scaler-resizes-only sub-option to yes
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- OS X/cocoa: fix charcode retrieving for accented characters
-- TOOLS/lua/ao-null-reload.lua: send ao-reload on audio-device-list change (issue #2738)
-- TOOLS/lua/autoload.lua: remove the extension prior to sort
-- Windows: fix dropping URIs (issue #2782)
-- af_lavrresample: prevent channels from being dropped, e.g. when going 7.1 -> 7.1(wide) and similar cases
-- ao_coreaudio: fix 7.1(rear) channel mapping
-- ao_openal: wipe out global context on init error (PR #2719)
-- ao_wasapi: avoid under-run cascade in exclusive mode
-- ao_wasapi: set buffer size to device period in exclusive mode
-- audio: fix spdif PCM fallback
-- build: add special openbsd case for iconv check (issue #2710)
-- command: fix NULL pointer deref in "video-codec" property (issue #2729)
-- command: fix track cycling logic (issue #2784)
-- demux: disable stream cache if no tracks are selected (issue #2692)
-- demux_mkv: add hack to fix opus gapless behavior
-- demux_mkv: support channel layout in VfW muxed PCM (issue #2820)
-- osc: fix runtime enable_osc(true/false)
-- player: fix initial audio sync in certain cases (issue #2770)
-- player: honor --force-window if video is selected, but inactive
-- player: never show "DS: (unavailable)"
-- player: restore old/correct --force-window behavior (issue #2825)
-- player: rewrite timeline/ordered chapter support
-- vaapi: fix compilation on older FFmpeg/Libav (issue #2737)
-- vdpau: force driver to report preemption early
-- video: don't wait for last video frame in the normal case (issue #2745)
-- video: fix coverart switching
-- video: slightly improve video stream switching
-- vo_opengl: add precision qualifier to usampler2D on ANGLE (issue #2761)
-- vo_opengl: default to rgba16f FBOs on ANGLE
-- vo_opengl: don't use normalized coords for debanding rectangle textures (issue #2831)
-- vo_opengl: dxinterop: fix compatibility issue with Vista
-- vo_opengl: pass the correct target to deband functions with Apple hwdec interop
-- vo_opengl: rename custom shader entrypoint from sample to sample_pixel (issue #2733)
-- x11: get *current* XRandR screen configuration instead of polling for new screens, too
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.15.0..v0.16.0` in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.15.0...v0.16.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.15.0..v0.16.0
-
-
-
-Release 0.15.0
-==============
-
-Build System Changes
---------------------
-
-- OS X bundle: remove git sha from the Info.plist version (issue #2677)
-- add "lua51" ("51obsd") to list of possible lua names
-- add option to customize config files system path (issue #2704)
-
-
-Features
---------
-
-New
-~~~
-
-- vo_opengl: implement support for transparent video display on OS X (alpha=yes suboption)
-- vo_opengl: use a checkerboard pattern as background for transparent video by default
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- add --audio-file-paths (issue #2632)
-- player, stream_dvb: implement dvb-channel-name property, add switch binding
-
-
-Changed
-~~~~~~~
-
-- vf_stereo3d: add alternating modes
-- vo_opengl: disable pbo by default for opengl-hq due to driver problems
-- vf_yadif: change defaults (issue #2539)
-- command: change heuristic for files with 1 chapter (issue #2550)
-- demux_mkv: adjust subtitle preroll defaults
-- exclude 360 from --video-rotate range (issue #2647)
-- osd: make osd-width/height properties watchable
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- ao_pulse: check for sample rate bounds, attempt fallback (issue #2654)
-- ao_wasapi: remove volume "restore" on exit
-- demux_cue: better error resilience
-- mixer: fix volume initialization with --af=volume
-- mpv.desktop: add audio/mp4 mime type
-- player: detect audio PTS jumps, make video PTS heuristic less aggressive
-- player: make watch later/resume work when "playing" directories
-- player: reset playback abort when reloading a file (issue #2568)
-- recognize frame sequenced 3D Matroska video
-- stream_dvb: fix channel switching
-- vaapi: add VP9 profile (requires VA-API 0.38.1 or newer)
-- vo_opengl: dxinterop: prevent crash after lost device
-- vo_opengl: enable brightness/contrast controls for RGB
-- vo_opengl: fix operation on GLES 2.0
-- vo_opengl: fix operation on GLSL versions earlier than 1.30
-- vo_opengl: flip screenshot image if backend uses flipped rendering (issue #2635)
-- vo_opengl: reset nnedi3 weights properly (issue #2661)
-- vo_rpi: handle rotation
-- vo_rpi: work around firmware oddness leading to incorrect video rect
-- windows: fix fd://
-- ytdl: Include Referer header as well
-- TOOLS/zsh.pl: add .opus extension in zsh completions
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.14.0..v0.15.0` in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.14.0...v0.15.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.14.0..v0.15.0
-
-
-
-Release 0.14.0
-==============
-
-Build System Changes
---------------------
-
-- build: install scalable svg icon as well
-- vo_opengl: require --enable-gpl3 for nnedi
-- win32: enable internal pthreads wrapper by default
-
-
-Features
---------
-
-New
-~~~
-
-- vo_opengl: add experimental dxinterop backend (renders with OpenGL, displays through Direct3D; broken with Intel drivers)
-- vo_opengl: add initial ANGLE support
-- windows: implement icc-profile-auto
-- windows: support taskbar button progress indicator (issue #2399)
-
-Removed
-~~~~~~~
-
-- Windows XP support
-- demux: remove old subtitle parser (FFmpeg users are unaffected; Libav loses support for some minor formats)
-- demux_libass: remove this demuxer (libavformat takes its place)
-- stream: drop old Linux PVR support
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- command, vo: add estimated-display-fps property
-- command: add vsync-ratio property
-- command: export some per-video-frame information (issue #2444)
-- vo_opengl: make LOOKUP_TEXTURE_SIZE configurable
-- windows: add option to set VO MMCSS profile
-
-
-Changed
-~~~~~~~
-
-- --sub-fix-timing now applies to ASS subtitles as well
-- command: rename vo-missed-frame-count property to vo-delayed-frame-count
-- input.conf: add default bindings for changing window scale (issue #2500)
-- videotoolbox: make decoder format customizable
-- vo_opengl: disable interpolation without display-sync
-- vo_opengl: make tscale=mitchell:tscale-clamp the default
-- vo_rpi: add an option to disable OSD
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- af_lavrresample: clamp float output to range
-- ao: disambiguate default device list entries
-- ao_alsa: filter audio device list
-- ao_alsa: list bidirectional devices too
-- ao_openal: accommodate more sample formats (issue #2494)
-- ao_openal: fix virtual speaker positioning
-- ao_wasapi: only report per-app volume in shared mode
-- ao_wasapi: work around DTS passthrough failure
-- build: make vaapi-wayland depend on gl-wayland (issue #2476)
-- demux: fix seeking in .ts
-- demux_lavf: mark ASS tracks as always UTF-8
-- demux_mkv: fix incremental indexing with single-keyframe files (issue #2498)
-- drm: fix setting up connectors
-- dxva2: reject 10 bit HEVC (issue #2516)
-- player: replace mistimed-frame-count with vsync-ratio on status line
-- sd_ass: fix secondary subtitle mode
-- various display-sync improvements
-- vo_opengl: enable NNEDI3 prescaler on OpenGL ES 3.0
-- vo_opengl: enable colormatrix even for RGB input
-- vo_opengl: fix backend autoprobing, attempt to improve GLX vs. EGL backend detection
-- vo_opengl: fix backend=x11 on Intel
-- vo_opengl: fix issues with some obscure pixel formats (e.g. rgb555)
-- vo_opengl: fix precision loss of fruit dithering matrix
-- vo_opengl: force dumb mode if RG textures are not available
-- vo_opengl: improve boundary check for polar filters
-- vo_opengl: various GLES compatibility improvements
-- vo_opengl: win32: test for exclusive mode
-- vo_opengl_cb: do not block on flipping when redrawing
-- vo_rpi: set aspect ratio
-- win32: fix console output with raw stdio functions
-- windows: try to avoid detection as exclusive fullscreen window (issue #2177)
-- x11: request bypassing compositor (issue #2502)
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.13.0..v0.14.0` in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.13.0...v0.14.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.13.0..v0.14.0
-
-
-Release 0.13.0
-==============
-
-NOTE: The previous release changed the default format for youtube-dl and
-removed some workarounds related to MPEG DASH support. However, the required
-changes to FFmpeg code (FFmpeg commit 4ab56667594842283dc5ae07f0daba2a2cb4d3af)
-are not in any FFmpeg release yet (as of 2015-11-10), so for now, playing DASH
-streams requires using FFmpeg git master. You can work around this by using the
---ytdl-format=best option.
-
-
-Features
---------
-
-New
-~~~
-
-- SVG version of the icon (with symbolic counterpart)
-- stream_libarchive: add multivolume support
-- vo_opengl: add prescaling framework along with Super-xBR and NNEDI3 (currently very slow) prescalers (issue #2230)
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- command: add mistimed-frame-count property
-- vo_opengl: add vsync-fences option
-
-
-Changed
-~~~~~~~
-
-- command: make display-fps property writable
-- options: enable mpeg2 hw decoding by default if hw decoding is requested
-- vo_opengl: rename fancy-downscaling to correct-downscaling
-- vo_opengl: correct-downscaling: enable also for anamorphic clips
-- vo_opengl: rename "drm_egl" to "drm-egl"
-- vo_opengl: disable drm-egl autopickup
-- vo_opengl: never load vaapi GLX interop by default
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- ao_alsa: fix 7.1 over HDMI
-- audio: do not require full audio chain reinit for speed changes
-- rpi: add support for codecs other than h264 (mpeg2 for now)
-- vd_lavc: make hwdec fallback more tolerant
-- video: fix playback of pal8
-- video: multiple display-sync fixes
-- vo: fix no-audio mode with interpolation enabled/display-sync disabled
-- vo_direct3d: fix operation (issue #2434)
-- vo_drm: handle possible errors from sigaction
-- vo_drm: show osd in audio only mode
-- vo_opengl: do not attempt to cache frames in FBO in dumb-mode (issue #2432)
-- vo_opengl: win32: always request MMCSS for DWM
-- vo_opengl: win32: try to enable DwmFlush by default
-- vo_vdpau: check VDP_RGBA_FORMAT_A8 support
-- win32: request MMCSS "Playback" profile
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.12.0..v0.13.0` in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.12.0...v0.13.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.12.0..v0.13.0
-
-
-Release 0.12.0
-==============
-
-NOTE: This release changes the default format for youtube-dl and removes some
-workarounds related to MPEG DASH support. However, the required changes to
-FFmpeg code are not in any FFmpeg release yet (as of 2015-10-29), so for now,
-playing DASH streams requires using FFmpeg git master. You can work around
-this by using the --ytdl-format=best option.
-
-
-Features
---------
-
-New
-~~~
-
-- vo_opengl: support new VAAPI EGL interop (requires Mesa 11)
-- vo_opengl: vaapi: add Wayland support
-- bring back the x11 video output
-- vo_opengl: support all kinds of GBRP formats
-
-Removed
-~~~~~~~
-
-- video: remove VDA support (VideoToolbox is preferred)
-
-Behavior
---------
-
-- vo_opengl: enable X11 EGL backend by default (disabled for NVIDIA due to driver bugs)
-- ytdl: don't override user-set format in no-video mode
-- sub: adjust behavior on mismatching video/subtitle aspect ratio
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- audio: add option for falling back to ao_null
-- options: add support for client certificate authentication
-- input: add key name for U+3000 IDEOGRAPHIC SPACE
-- player: add audio drop/duplicate mode to video-sync
-
-Changed
-~~~~~~~
-
-- vo_opengl: remove sharpen scalers, add sharpen sub-option
-- vo_opengl: make sw suboption work without explicit backend selection
-- command: make time properties unavailable if timestamp is unknown
-- command: do not return 0 for bitrate if unknown (make property unavailable instead)
-- vo_opengl: make the default debanding settings less excessive
-- ytdl: disable --all-subs if "sub-lang" is in raw-options
-
-Removed
-~~~~~~~
-
-- video: remove user-controllable PTS sorting (--pts-association-mode)
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- player: fix another --force-window bug
-- player: add wav to list of external audio file extensions
-- ao_alsa: fix failure to find any sample format
-- player: make stop command actually stop in all cases
-- audio: various fixes related to audio device hotplugging
-- vo_xv: fix crash with --wid
-- ytdl: Remove DASH hacks, use DASH by default
-- player: be slightly less prone to framedrop in display sync mode
-- sd_lavc: extend subtitle resolution if images go outside video frame
-- player: offset chapter display by start time
-- command: make bitrate properties work correctly for external tracks
-- w32_common: disable IME
-- player: fix display-sync A/V calculation on high playback speeds
-- player: fix display sync A/V difference estimation on drops
-- player: raise display sync desync tolerance
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.11.0..v0.12.0` in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.11.0...v0.12.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.11.0..v0.12.0
-
-
-Release 0.11.0
-==============
-
-Features
---------
-
-New
-~~~
-
-- vo_opengl: implement debanding
-
-Removed
-~~~~~~~
-
-- audio/filter: remove center, extrastereo, karaoke, sinesuppress, sub,
- surround, sweep, ladspa, hrtf, export and bs2b filters (these are either
- considered useless or have replacements in lavfi)
-- video/filter: remove lavfi wrappers for noise, hqdn3d, unsharp and delogo
- (these filters remain usable through lavfi)
-
-Behavior
---------
-
-- vo_opengl: require FBOs by default (use dumb-mode suboption for old hardware
- and broken drivers)
-- vo_opengl: enable debanding by default for the opengl-hq preset
-- audio/out: use new sample format determination code
-- player: prefer logical current directory path (affects logic for resuming
- playback)
-- vf_vdpaurb: Pass through non-hardware-decoded content
-- player: make force-window=immediate work in auto-profiles
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- af_lavrresample: add normalize suboption
-- vo_opengl: add deband, deband-iterations, deband-threshold, deband-range and
- deband-grain suboptions
-- af_lavfi: implement af-metadata property (like vf-metadata)
-
-Changed
-~~~~~~~
-
-- command: make "add <property> 0" not change the value
-
-Removed
-~~~~~~~
-
-- vo_opengl: remove source-shader suboption
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- options: fix --no-config
-- cache: do not include backbuffer size in total stream cache size
-- audio/format: actually prefer float over double sample format for
- int->float conversions
-- audio/format: fix interlaved vs. non-interleaved conversions
-- audio/format: revise format conversion scoring
-- video: make --field-dominance set interlaced flag
-- vf: vf_stereo3d compilation depends on libavfilter
-- vf_yadif: add hack for Libav compatibility
-- player: add opus to list of external audio file extensions
-- build: allow disabling vapoursynth completely
-- libmpv/win32: allow multiple windows at the same time
-
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.10.0..v0.11.0` in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.10.0...v0.11.0 or
-http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.10.0..v0.11.0
-
-
-Release 0.10.0
-==============
-
-
-Features
---------
-
-New
-~~~
-
-- uchardet support
-- Matroska: reading cue sheets embedded in tags
-- Support for VideoToolbox hardware decoding
-- Display sync mode (--display-sync)
-- --force-window=immediate mode
-- fd:// protocol
-- libarchive wrapper for reading compressed archives
-- TOOLS/lua: zones.lua
-- Support for the "new" libavcodec VDPAU API
-- vf_vdpaurb, Add a new filter for reading back VDPAU decoded frames
-- DXVA2: HEVC support
-- Enabled HEVC profiles with VA API
-- HEVC added to whitelist of hwdec codecs
-- vo_null: framerate emulation
-- vo_opengl: support for custom shaders
-- vo_opengl: temporal-dither-period option
-- vo_opengl: tscale-clamp option
-- vo_opengl: option to attach target-prim/target-csp to window screenshots
-- vo_opengl_cb: "block" framedrop mode (now default)
-- vo_opengl_cb: support for interpolation
-- vo_vdpau: rotation support
-- ytdl_hook: support for 'multi_video' results
-
-
-Removed
-~~~~~~~
-
-- af_convert24 (af_lavrresample does this now)
-- af_dummy
-- audio: S8, U16, U24, U32 formats
-- DVD and BD menu support
-- TOOLS: youtube-starttime.lua
-- VA API: compatibility crap (< 0.34.0) and vo_vaapi deinterlacer
-- vo_x11
-
-
-Deprecated
-~~~~~~~~~~
-
-- vf_dlopen
-
-
-Behavior
---------
-
-- audio: softvol scale is now cubic
-- cache: readahead size is limited to half the cache size at the beginning
-- charset_conv: "auto" encoding detection now prefers uchardet
-- demux_playlist: skip hidden directories
-- input.conf: O toggles between 2 states only
-- input.conf: Ctrl+s key binding for window screenshots
-- input.conf: mouse volume control (horizontal scrolling) inverted
-- input.conf: L to toggle infinite looping
-- input.conf: remap d/D keys (dropped framedrop cycle, replaced with deinterlacing toggle)
-- player: disabled seeking on unseekable streams even if the cache is enabled
-- player: parses and exposes m3u playlist titles
-- player: --term-playing-msg is now in a separate log category
-- player: removed automatic DVB channel advancement on no data
-- player: now restores video-aspect on playback resume
-- player: now uses exit code 0 by default for quit, 4 for signals, etc.
-- player: warns against using HLS URLs with --playlist
-- screenshots: changed the default directory in pseudo-gui mode to desktop
-- screenshots: screenshot directory is now created automatically
-- screenshots: default template is now prefixed with "mpv-"
-- TOOLS/lua/autoload: adds all files on start
-- vo: vo_wayland moved up in autoprobe list
-- vo_opengl: enabled pbo by default with opengl-hq
-- vo_opengl: cache dir for ICC profiles is now created automatically
-- w32: shift drag and drop appends
-- x11: shift drag and drop appends
-
-
-Options and Commands
---------------------
-
-Added
-~~~~~
-
-- af_volume: replaygain-fallback option
-- ao_coreaudio: change-physical-format option
-- ao_coreaudio: exclusive option
-- ao_null: channel-layouts option for testing channel layout selection
-- audio: --audio-spdif as new method for enabling passthrough
-- cache: --cache-backbuffer to configure cache backbuffer size
-- command: define-section command for defining input bindings
-- command: audio-params and audio-out-params properties
-- command: keypress, keydown, and keyup commands
-- command: playlist_shuffle command
-- command: option-info/N/set-locally property indicating per-file options
-- command: protocol-list property
-- command: track-list/N/audio-channels property
-- demux: --demuxer-max-packets and --demuxer-max-bytes options to control maximum queue size
-- input: relative percentage seek
-- osc: time display configuration options
-- player: --playlist-pos option
-- screenshots: --screenshot-jpeg-source-chroma option to disable JPEG 4:4:4 output
-- screenshots: --screenshot-high-bit-depth option to allow or disallow 16 bit output
-- screenshots: --screenshot-directory option
-- sub: --stretch-image-subs-to-screen option for stretching image subtitles to screen
-- TOOLS/stats-conv: allow passing regex via command line
-- video: --video-aspect-method option to configure container vs. bitstream aspect ratio
-- vo_drm: mode suboption to set the mode ID to use
-- vo_opengl_cb, vo_opengl: --hwdec-preload option for preloading hwdec context
-- vo_rpi: background disabled by default
-- vo_xv: buffers suboption to configure number of buffers
-- win32: portable config mode
-
-
-Changed
-~~~~~~~
-
-- audio: changed the range of the volume option/property (0 is still silence, and 100 now always means unchanged volume)
-- command: allow changing deinterlace property any time
-- command: allow changing track properties while no file is loaded
-- command: always make video-aspect property accessible
-- command: better choice when to allow playback-related commands
-- command: change OSD symbol for absolute perc. seek
-- command: change the default action for rescan_external_files
-- command: change the hwdec properties
-- command: define-section with empty contents removes a section
-- command: export stereo 3D tags
-- command: make auto-deinterlacing output at field rate
-- command: make deinterlace property use interlaced-only yadif mode
-- command: make property event mask matching more restrictive
-- command: make the playback-time property writable
-- input: allow - as separator between commands, instead of _
-- options: --loop without argument means looping forever
-- options: make keyvalue list parsing less strict
-- player: extend --hls-bitrate option
-- vf_yadif: expose interlaced frame mode
-- video: --video-stereo-mode=no to disable automatic stereo conversion
-- vo_opengl_cb, vo_opengl: --hwdec-preload for preloading hwdec context
-- vo_opengl: replace icc-cache with icc-cache-dir
-- vo_opengl: icc-profile overrides icc-profile-auto
-
-
-Renamed
-~~~~~~~
-
-- command: rename audio-format property to audio-codec-name
-- options: rename --media-title option to --force-media-title
-- vo_opengl: rename use_full_range to use_normalized_range
-
-
-Deprecated
-~~~~~~~~~~
-
-- --ad-spdif-dtshd (use --audio-spdif=dts-hd)
-- audio-samplerate property
-- length property
-
-
-Removed
-~~~~~~~
-
-- get_property command
-- --demuxer-readahead-packets and --demuxer-readahead-bytes
-- image_writer: don't use jpeg baseline, and remove useless jpeg-optimize and jpeg-baseline options
-- --leak-report
-- --slave-broken
-- vo_opengl: npot suboption
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- ad_spdif: use DTS-HD passthrough only if the audio is really DTS-HD
-- af: fix behavior with filter chains that require a large number of auto-inserted conversion filters
-- af_lavcac3enc: fix A/V sync
-- ao_alsa: accept 7.1 over HDMI
-- ao_alsa: refuse to use spdif if AES flags can't be set
-- ao_wasapi: fix crash on hotplug init error
-- audio: avoid wasting CPU due to continuous wakeup
-- audio: do not exit when loading small files in paused mode
-- audio: fix channel map fallback selection
-- audio: fix crash on uninit
-- audio: fix --end handling
-- audio: fix EOF state with --keep-open
-- audio: fix restoring volume
-- charset_conv: fix switched parameters
-- charset_conv: use our own UTF-8 check with ENCA only
-- cocoa: don't load hardcoded icon if running from bundle
-- cocoa: hide cursor using a blank image instead of a system-wide API
-- command: do not exit playback if the B point of A-B loop is past EOF
-- command: fix audio-out-detected-device property
-- command: fix track property when no file is loaded
-- command: fix video-aspect property update notification
-- command: let track properties return option value in idle mode
-- demux: don't get stuck on some cases of timestamp resets
-- demux: handle Matroska-style replaygain tags as well
-- demux_lavf: do a better job at guessing the vobsub .sub filename
-- demux_mkv: disable ordered chapters if ChapterTimeEnd is missing
-- demux_mkv: discard broken index
-- demux_mkv: fix mpeg2 mapping
-- demux_mkv: ignore deprecated FrameRate, do not assume PAL
-- demux_mkv: improve video duration detection heuristic
-- demux_mkv: parse FLAC channel layouts
-- demux_playlist: make mime type comparison case-insensitive
-- dxva2: fix handling of cropped video
-- idet.sh: Support larger files
-- mp_image: fix vf_vdpaupp references
-- options: fix conversion of flags to strings
-- options: move program name to end of window title
-- options: remove the period at the end of "No file."
-- osc: completely disable if no VO window exists
-- osc: exit tick immediately if disabled
-- osc: reinit on playlist changes
-- osx: add NULL check for input context in a missing case
-- player: fix crashes when adding external tracks before loading main file
-- player: increase tick event update frequency
-- player: make decoding cover art more robust
-- player: raise maximum idle time
-- player: return better guess for playback time during seeks
-- player: show larger cache sizes in MB on status line
-- player: slim down A/V desync warning
-- sd_ass: assume negative durations are unknown durations, and handle them
-- terminal: disable terminal foreground state polling
-- terminal-unix: set terminal mode on init
-- timer: fix a corner case on clock changes
-- TOOLS: make autodeint detect telecine in parallel
-- TOOLS/zsh.pl: die loudly if mpv fails to run
-- vaapi: prefer direct display over copy-back
-- vaapi: fix some videos only showing up green
-- vaapi: treat cropped decoder output slightly more correctly
-- vda: add support for nv12 image formats
-- vd_lavc: fix a hw decoding fallback case
-- vf_stereo3d: drop internal implementation
-- vf_vavpp: don't attempt to deinterlace progressive frames
-- vf_vavpp: fix bob deinterlacing for bottom field first videos
-- vf_vdpaupp: Don't crash when evaluating interlacing of NULL mpi
-- video: always re-probe auto deint filter on filter reconfig
-- video: better heuristic for timestamp resets
-- video: fix panscan in vertical case
-- video: fix VideoToolbox/VDA autodetection
-- video: unbreak EOF with video-only files that have timestamp resets
-- vo_direct3d: fix broken pseudo GUI drag and drop hint
-- vo_drm: fix centering with regard to stride
-- vo_drm: fix crashes with --profile=pseudo-gui
-- vo_drm: fix resolution not restored after exiting
-- vo_drm: fix stride problem for certain devices
-- vo_drm: make VT switching non mandatory
-- vo: free frames before killing VO
-- vo_opengl: avoid broken shader if hwdec fails to provide textures
-- vo_opengl_cb: drop frames eagerly if frames are not rendered
-- vo_opengl: CMS no longer implies linear scaling
-- vo_opengl: fix alpha video in one case
-- vo_opengl: fix dangling pointers with vo_cmdline
-- vo_opengl: fix framestepping/pausing + interpolation
-- vo_opengl: fix "freezes" after seeking with interpolation on
-- vo_opengl: fix scale=oversample's threshold calculations
-- vo_opengl: framebuffers work under GLES 2
-- vo_opengl: improve robustness against PBO failure
-- vo_opengl: reimplement tscale=oversample
-- vo_opengl: reject future images in different formats
-- vo_opengl: X11: don't leak when GL init fails
-- vo: restore frame-drop logic for high-fps clips
-- vo_rpi: fix blackscreen before the first subtitle/OSD is rendered
-- vo_rpi, vo_opengl: do not globally terminate EGL on VO uninit
-- vo_sdl: fix glaring memory leak
-- vo_vdpau: check maximum video size
-- vo_vdpau: limit output surfaces to allowed maximum dimensions
-- win32: fix window resize logic
-- win32: fix crashes when changing system time
-- x11: Handle external fullscreen toggles
-- ytdl: catch bogus extractor info
-- ytdl: do not use deprecated media-title option
-- ytdl: don't print failure warning when youtube-dl was killed by us
-- ytdl: get start_time
-
-This listing is not complete. Check DOCS/client-api-changes.rst for a history
-of changes to the client API. A complete changelog can be seen by running
-`git log v0.9.2..v0.10.0` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.9.2...v0.10.0.
-
-
-Release 0.9.2
-=============
-
-Changes
--------
-
-- The Lua check now also checks for `lua52.pc`, as used by Arch Linux testing.
-- (X11) `vo_opengl`'s `icc-profile-auto` now queries the current ICC profile
- relative to the center of the window.
-- `ao_coreaudio`, `ao_alsa` now support adding dummy padding channels for
- better compatibility with hardware decoders that only support specific
- channel counts (e.g. 5.1 now should work on a decoder that only accepts 7.1).
-- Channel fallback (in case the audio device doesn't natively support a given
- channel layout) has been improved.
-- `vf_vapoursynth` now rejects unaligned video instead of outputting corrupted
- video.
-- mpv now tries to autoload `.sup` subtitles as well.
-
-Bug fixes
----------
-
-- `vo_opengl`'s default for `fbo-format` is now `rgba16`, to avoid rounding
- errors when using non-default `cscale` (issue #1918).
-- Improved framedrop behavior when playing video that has higher framerate
- than the display (issue #1897).
-- Trying to play a directory will no longer spam `Connection lost!` to the
- console log.
-- (Linux) Several `vo_rpi` bugfixes.
-- (Linux) Several `vo_drm` bugfixes. Pan&Scan is now supported.
-- (X11) Fix fullscreen behavior on certain window managers (issues #1937,
- #1920).
-- (OSX) The OSD no longer always shows up on startup.
-- (OSX) Several `ao_coreaudio` and `ao_coreaudio_exclusive` bugfixes.
-- (OSX) Fixed potential crash on exit when using Cocoa.
-- (ClientAPI) `vo_opengl_cb` now actually applies options changed at runtime.
-- (OSX, ClientAPI) Cocoa now works when both the `cplayer` (`mpv`) and `libmpv`
- are built at the same time; however, `libmpv` now always creates an
- application singleton. Cocoa has to be disabled completely to prevent
- `libmpv` from creating the singleton.
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.9.1..v0.9.2` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.9.1...v0.9.2.
-
-Release 0.9.1
-=============
-
-Changes
--------
-
-- mpv's IRC channel moved from #mpv-player to #mpv on chat.freenode.net.
-- Documentation updates.
-- The default value for the `--ytdl-format` option is now `best`, in order to
- work around `youtube-dl`'s 2015.04.26 release enabling DASH by default, as
- FFmpeg / Libav do not yet properly support DASH.
-- When seeking, the current timestamp will show the predicted seek timestamp
- instead, until the final timestamp is resolved. Improves UI responsiveness
- on slow streams and/or large seeks.
-
-Bug fixes
----------
-
-- Corrected the release marker on DOCS/client-api-changes. The release manager
- forgot to fix it before release...
-- Fix `vo_vdpau` rendering garbage lines on H.264 video with non-mod16 size
- (issue #1863).
-- Fix a crash on exit if the "sub_reload" command had run successfully.
-- Fixed seeking with the mouse when `osc-seekbarstyle=bar` is set (issue
- #1876).
-- (IPC) Fixed encoding of UTF-8 data in JSON (issue #1874).
-
-New features
-------------
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.9.0..v0.9.1` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.9.0...v0.9.1.
-
-Release 0.9.0
-=============
-
-Changes
--------
-
-Changes that may break users' config files have been annotated with a `(!)`.
-
-- Note: mpv is not compatible with Lua 5.3. Lua 5.1 or 5.2 is required.
-- The minimum required libass version is now 0.12.1 or newer.
-- The minimum required FFmpeg version is now 2.4.0 (equiv. Libav 11) or newer.
-- The internal libmpg123 support was removed. This was already not used by
- default in the previous release.
-- `(!)` The LIRC support was removed. Configure LIRC remotes as input devices
- instead.
-- `(!)` The Linux Joystick support was removed.
-- `(!)` `vf_screenshot` was removed, as they are now handled at a VO level and
- is compatible with all VOs.
-- `(!)` `--ass-use-margins` has been renamed to `--sub-use-margins` and applies
- only to plain-text (non-ASS) subtitles (enabled by default). The new
- `--ass-force-margins` option applies only to ASS subtitles (disabled by
- default). To get the old behaviour back, enable both at the same time.
-- `(!)` The `--sub-scale-with-window` option now only applies to plain text
- (non-ASS) subtitles (enabled by default). The new `--ass-scale-with-window`
- option does the same but only with ASS subtitles (disabled by default).
-- `(!)` The range for the `param1` for the `gaussian` `vo_opengl` scaler has
- been redefined. Instead of being an arbitrary 1-100 range, have a default
- value of 1.0, and anything higher is blurrier.
-- `(!)` The `seek`, `playlist_next, `playlist_prev`, `loadfile` and `loadlist`
- parameters no longer accept numerical parameters where symbolic parameter
- names exist.
-- `(!)` `vo_opengl` changes:
- - `(!)` The `smoothmotion` suboption has been renamed to `interpolation`.
- The old name is still supported for now.
- - `(!)` The `bilinear_slow` scaler has been renamed to `triangle`.
- - `(!)` `scale-down` has been renamed to `dscale` and now has its own set of
- config options (e.g. `dscale-radius`).
- - `(!)` Scaler radius no longer defaults to `3` but to a preferred value
- that may be different for each filter.
- - The `scale-radius` option may now go down as low as `0.5`, which is the
- value used by the `nearest` filter.
- - `spline36` is the new `cscale` default for `opengl-hq`. This might break
- setups that use `fbo-format=rgb8`. To work around it, leave `fbo-format`
- as its default, or set to something higher than 8, or set `cscale=bilinear`,
- the previous default.
-- `(!)` `vf_format` no longer converts video to YUYV if there is no parameter.
- Video is now passed unchanged unless a format is specifically requested.
-- `(!)` The `--colormatrix`, `--colormatrix-input-range`,
- `--colormatrix-output-range` and `--colormatrix-primaries` options have been
- converted into `vf_format` suboptions. See commit 27715b7 and the manual for
- details.
-- `vf_mirror`'s implementation was replaced with calling into `libavfilter`'s
- `vf_hflip` filter, thus depending on `libavfilter` to function.
-- The `device` subption to `ao_wasapi` has been deprecated in favor of
- `--audio-device`.
-- `--video-rotate` now allows 360 as an argument instead of stopping at 359.
-- Several improvements to `af_scaletempo`.
-- Options that have multiple options and also include a "yes" option now
- default to that if specified with no arguments.
-- The default value of `--cache-default` is now 150000 (153.6 MB, ~146 MiB).
-- JPEG screenshots now use the same subsampling as the source video. The images
- are still RGB regardless of source format though.
-
-Bug fixes
----------
-
-- mpv no longer saves position on files that can't be resumed (issue #1701).
-- (X11) Fix the player thinking the mouse has left the window in some WMs /
- embeddings (issue #1672).
-- mpv no longer freezes on wayland when the compositor stops asking it to draw
- itself (e.g. when minimized) (issue #249).
-- `.ac3` files are no longer rejected by `--audio-file-auto` (issue #1759).
-- `ao_wasapi` now automatically enables `exclusive` when passthrough is
- attempted (issue #1742).
-- Attempt to fix flickering on Intel VAAPI drivers with `--hwdec=vaapi` and
- `--vo=opengl` (issue #1765).
-- `youtube-dl` will no longer download video streams when video playback
- is disabled with `--no-video`.
-- (Windows) mpv now prevents system sleep when playing a video-only file.
- Previously, only files with an opened audio track would prevent sleep.
-
-New features
-------------
-
-- `vo_opengl` features:
- - Added `ewa_ginseng`, `ewa_hanning`, `robidoux`, `robidouxsharp`,
- `oversample` and `haasnsoft` scalers.
- - There are now `ewa_lanczossoft` and `ewa_lanczossharp` aliases to
- `ewa_lanczos` that are tuned to be blurrier and sharper, respectively.
- - Added `gamma-auto` option that uses ambient light sensors to automatically
- adjust the video gamma. See commit c028d78 for details.
- - Added `blend-subtitles` option to draw subtitles directly into the video
- instead of rendered afterwards. Potentially necessary for correct rendering
- with files that use ASS subtitles for typesetting in combination with an
- `icc-profile`. There is a default option for drawing after upscaling,
- and a `video` option for drawing before upscaling. See details and warning
- on the manual.
- - There is now a `tscale` option, used to choose the temporal scaler used
- in the `interpolation` mode (previously `smoothmotion`).
- - There is a new `scale-blur` parameter to adjust the amount of blur that
- most of the filters produce. Deviating from the default may introduce
- artifacts in EWA filters.
- - (Windows) There is now a `dwmflush` option that might help improve
- rendering of high-fps video. Disabled by default. See manual for options.
-- New Linux-only `vo_drm` video output driver. Uses the direct rendering /
- kernel modesetting drivers to draw directly to the framebuffer, but with
- no hardware acceleration. See manual for details.
-- New `pseudo-gui` builtin profile, automatically used when launched from
- `mpv.desktop` by opening `mpv.exe` on windows (`mpv.com` still works as
- usual), or by opening the `mpv.app` bundle. The `pseudo-gui` tries to make
- the player window behave closer to what a desktop player would do, by not
- immediately closing and allowing the user to drag&drop files for playback.
- See manual for details.
-- mpv can now play directories by automatically playing their contents instead.
- Works everywhere but on Windows, due to issues with Windows' C runtime.
-- Add support to pitch correct stretched audio with librubberband.
-- Add support for the Raspberry Pi 2's hardware decoder when FFmpeg (or Libav)
- is built with `--enable-mmal`. See commit 8fff125 for details.
-- The `--cache` option now accepts a "yes" option, that always enables a
- `--cache-default`-sized cache on all cases a cache can be used, unless
- `--cache-default` disables caching.
-- `ao_pulse`, `ao_coreaudio` and `ao_wasapi` now support device hotplugging.
-- New `--osd-align-x` and `--osd-align-y` options can be used to align the OSD
- independently from subtitles.
-- New `--osd-bold` and `--sub-text-bold` options can be enabled to bold all
- OSD or plain-text subtitle text, respectively.
-- Added a default keybind to the `u` key that enables/disables ASS style
- overriding. When enabled, is equivalent to `--ass-style-override=force`.
-- There is now a `MOUSE_ENTER` keybind, that is called when the mouse cursor
- enters the VO from outside.
-- The new `--ytdl-params` option can be set to arguments that are always given
- to `youtube-dl` invocations. There is no sanity checking, so invalid options
- can prevent `mpv` from working with http URLs.
-- There's a new `--demuxer-mkv-fix-timestamps` option, enabled by default, that
- tries to guess more accurate video timestamps by using FPS information, if
- available. See manual for details.
-- The new `--window-scale` option can be used to scale the video window by the
- specified multiplier, before other options such as `--autofit` are applied.
-- `vo_direct3d_shaders` now supports NV12 colorspace without using stretchrect,
- but it seems no drivers actually support that.
-- (Client API) Added a `rescan_external_files` command, as requested on issue
- #1586.
-- (Client API) If enabled, initializing the Client API will now also load the
- user's `mpv.conf`.
-- (Client API) There's a new `mpv_opengl_cb_report_flip()` call for API users
- to call to inform mpv of when exactly a frame was displayed.
-- (Client API) The `mpv_opengl_cb_render()` function was deprecated in favor
- of the new, simpler `mpv_opengl_cb_draw()` function.
-- (Client API) There is a new `screenshot_raw` command. See `input.rst` for
- details.
-- The `--input-file` argument may now also be a file descriptor in the form
- "fd://N", where N is the FD number.
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.8.0..v0.9.0` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.8.0...v0.9.0 (only 250 commits).
-
-Release 0.8.3
-=============
-
-Changes
--------
-
-- Documentation fixes and updates.
-- (Client API) Clarify `mpv_opengl_cb_render`'s viewport parameter behavior.
-
-Bug fixes
----------
-
-- (X11) Fix crash on `vo_xv:no-colorkey` with the Overlay adapter. (bug #1629)
-- (X11) `--stop-screensaver` is now implemented by calling into
- `xdg-screensaver`, fixing some compatibility issues that prevented it from
- actually stopping screensavers.
-- Make the video equalizer work correctly on some VAAPI drivers. (bug #1647)
-- Prevent OSD from disappearing when clicking on mozplugger. (bug #1672)
-- The new DVB-S2 support code, using S2API, now builds on FreeBSD.
-- Fix decoding of seekable matroska from unseekable network stream. (bug #1656)
-- (OSX) Fix crashing when closing a VO's window (usually at exit). (bug #1657)
-- (OSX) Unhide the mouse cursor when over the Dock or Launchpad. (bug #513)
-- (OSX) Fix mouse cursor autohiding when the player is fullscreen.
-- `vf_vavpp` (VAAPI postprocessing) now deinterlaces video correctly.
-- `smb://` streams now use the stream cache (used to be inefficient).
-- (Windows) Do not hide the mouse cursor when it is hovering the window menu.
-- Fix 8-channel output on `ao_jack`. (bug #1688)
-- Fix `--mf-fps` parameter on JPEG files. (bug #1689)
-- Fix anamorphic scaling being ignored if it was very minor.
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.8.2..v0.8.3` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.8.2...v0.8.3.
-
-Release 0.8.2
-=============
-
-Bug fixes
----------
-
-- Fix OSD placement accidentally messed up in 0.8.1 (commit 22863d6).
-
-Release 0.8.1
-=============
-
-Changes
--------
-
-- DOCS/client-api-changes has been correctly updated for the v0.8.0 release
- series.
-- Quality and major perfomance improvements to smoothmotion.
-- Silence the "[ytdl_hook] WARNING: video doesn't have subtitles" warning.
-- Other documentation updates.
-
-Bug fixes
----------
-
-- Attempt to fix OpenGL shader compilation on Intel windows drivers. (bug
- #1536)
-- Attempt to improve the OpenGL shader's compatibility with GLES2.
-- (Windows) Fix noise when seeking while using wasapi:exclusive. (bug #1529)
-- Fix the waf-based build system when used with newer versions of waf with
- python3.
-- Documentation fixes. (includes bug #1608)
-
-New features
-------------
-
-- mpv now tries to load '.vtt' subtitles.
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.8.0..v0.8.1` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.8.0...v0.8.1.
-
-Release 0.8.0
-=============
-
-Changes
--------
-
-Changes that may break users' config files have been annotated with a `(!)`.
-
-- `(!)` vo_opengl_old has been removed. OpenGL rendering now requires hardware
- that can do at least OpenGL 2.1, the oldest version supported by vo_opengl.
-- `(!)` vf_pp has been removed. A version of it can still be accessed through
- lavfilter (e.g. --vf=lavfi=[pp...]), but it receives no QP information.
- Also, vf_dlopen no longer passes QP information to its loaded plugin either
- (it's always 0).
-- `(!)` vf_softpulldown, vf_swapuv, vf_phase, vf_divtc and vf_ilpack have all
- been removed. Ports or alternative versions of these filters are available
- through libavfilter (vf_lavfi).
-- vf_pullup and vf_noise now are simply wrappers to their libavfilter
- equivalents. Both are only currently available on FFmpeg (not Libav).
-- `(!)` ao_portaudio has been removed. There is good support for the native
- audio drivers of all major desktop platforms, as well as ao_sdl and
- ao_openal as fallbacks.
-- `(!)` vo_opengl's `lscale` suboption has been renamed to `scale`; `lradius`
- is now `scale-radius`, `lparam1` and `lparam2` are now respectively
- `scale-param1` and `scale-param2`; `lscale-down` is now `scale-down`.
- The `cscale` suboption remains as is.
-- `(!)` Several of the `vo_opengl` radius-preset aliases supported by `scale`
- have been removed; use `scale-radius` to set if needed. For example, use
- `--vo=opengl:scale=lanczos:scale-radius=2` instead of `scale=lanczos2`.
- The default radius is recommended for most filters.
-- `(!)` vo_opengl no longer supports the `stereo` suboption. The anaglyph
- effect can be reproduced with the stereo3d filter. The quadbuffer support,
- which requires expensive specialized hardware to begin with, is removed.
-- `(!)` The `approx-gamma` suboption to vo_opengl has been removed. The
- BT.1886 specification says that it's actually how it's supposed to be done
- so it is now the default when `srgb` or `icc-profile` are enabled. This does
- not include BT.1886's gamma drop.
-- `(!)` The `no-scale-sep` and `indirect` suboptions to vo_opengl have been
- removed. These are now autodetected and enabled whenever they would have
- benefit.
-- `(!)` The `--lua` and `--lua-opts` parameters / options are now called
- `--script` and `--script-opts`, respectively. The `lua` subdir of the mpv
- config dir is also now expected to be called `scripts` instead.
-- `(!)` The `--fixed-vo` option has been removed. It has been the default for
- a long time anyway, and disabling fixed-vo is not useful.
-- vo_opengl_hq has been updated to take into account new features.
- `fancy-downscaling` and `sigmoid-upscaling` are enabled,
- "mitchell" is now the default for `scale-down`.
-- The `sub-visibility` OSD message is now clearer about whether subtitles
- are hidden or just not available / selected.
-- The device IDs given to `--audio-device` for use with ao_coreaudio now use
- UIDs, so they don't change when devices are added/removed or after reboots.
-- `--msg-level` now also accepts ',' as separator.
-- (Client API) The client API now refuses to initialize if the LC_NUMERIC
- locale is not "C".
-- (Client API) The native type of the `msg-level` parameter is now a key-value
- list. Setting or reading it as a string still works.
-- (Slave API) The `get_property` command is now deprecated.
-- Documentation updates.
-- PDF documentation is now disabled by default due to rst2pdf being fickle,
- causing weird build errors.
-- Print desync messages with negative A/V sync as well. While rare, it could
- happen with some uses of `--autosync`.
-
-Bug fixes
----------
-
-- (Windows) Avoid resizing the video window when player is minimized, might
- address bug #1547.
-- (NetBSD) Fix build with v4l2.
-- (Linux) Attempt to address conflicts with the pulseaudio mixer. (bug #1578)
-- Multiple cdda:// fixes. (bugs #1555, #1560)
-- VP9 timestamps no longer cause "missing PTS" warnings with new enough FFmpeg
- builds.
-- Fix a crash when using H.264 hardware decoding on new enough libavcodecs.
- (bug #1587)
-
-New features
-------------
-
-- vo_opengl now supports frame blending to eliminate stuttering when the video
- framerate does not nicely match the display framerate through the
- `smoothmotion` suboption. This is not meant to artificially increase the
- video's FPS, so there is no "soap opera effect" or difficulties with some
- video types (e.g. anime).
-- vo_opengl now supports sigmoidal upscaling (e.g. for fullscreen), which
- reduces ringing induced by upscaling, enabled through the
- `sigmoid-upscaling` suboption.
-- vo_opengl now supports ewa_lanczos (Jinc) scaling, which provides higher
- quality with less aliasing. It supports an experimental `scale-antiringing`
- parameter, which tries to further reduce video ringing.
-- vo_opengl now has a `linear-scaling` suboption, that makes the scalers work
- in linear light. Implied by the `srgb`, `icc-profile` or the new
- `sigmoid-upscaling` suboption.
-- vo_opengl has improved downscaling for higher downscale ratios when
- `fancy-downscaling` is enabled.
-- Add `--keep-open=always` to make `--keep-open` apply to all files instead of
- only the last.
-- mpv now filters the tags that are printed on the console to try to hide
- useless metadata. The filter works as a whitelist, and can be configured
- with the `--display-tags` option.
-- Add a `--sub-scale-by-window` option that, when set to 'no', prevents
- subtitles from changing their pixel size when the window changes sizes.
-- vo_opengl now technically has GLES 2 and 3 support (but not GLES 1). GLES 3
- support is only tested on Mesa's software emulation. GLES 2 has been tested
- on nvidia drivers. Both GLES modes are feature deficient and inefficient due
- to GLES limitations, so they should not be preferred over the standard
- OpenGL mode.
-- (X11) vo_opengl now supports the `icc-profile-auto` option on X11 hosts.
-- The 'A' key now by default cycles through a list of preset aspect ratios.
- Meant to be used to work around broken files.
-- There is now a "force" mode for `--loop`. Works like "inf", but tries to
- open files/streams even if there was an error on the previous attempt.
-- There is now a `--log-file` option to write mpv's log messages to a specifed
- file.
-- There is now a `--audio-file-auto` option to automatically load an external
- audio file with certain constraints. Will only load external audio if the
- playing file has a video stream. Set to "exact" by default. (bug #967)
-- There is now a `--network-timeout` option to specify a timeout in seconds
- for network access. If 0 (default), uses the libavformat default. If a
- protocol that doesn't support timeouts is used, this option is ignored.
-- (X11) The XF86 special keys are now all mappable in `input.conf`. All keys
- documented in `XF86keysym.h` are available, but only as their numeric codes.
-- (Linux) The DVB implementation now supports DVB-S2 through S2API.
-- (Linux) The VDR format for `channels.conf` is now supported and preferred.
- See http://linuxtv.org/vdrwiki/index.php/Syntax_of_channels.conf for the
- syntax. Tuning to DVB-S2 channels requires the VDR-format `channels.conf`.
-- (Linux) There is now a `--dvbin-full-transponder` option for handling
- special broadcast cases where PIDs switch, or just to work around incomplete
- PID data.
-- (Client API) There is now an opengl_cb VO, which receives the OpenGL context
- from the libmpv client. This allows a client to render libmpv video directly
- to a provided OpenGL context without having to use --wid embedding.
-- (Client API) The aforementioned --wid parameter can now be set at any time,
- but has no effect if a file is already loaded.
-- (Client API) The list of DVD titles can now be queried.
-- (Client API) There is now a `filtered-metadata` property, containing only
- the tags allowed by `--display-tags`.
-- (Client API) There is now support for XEmbed `--wid` targets, such as a
- `GtkSocket`'s `gtk_socket_get_id()`.
-- (Client API) There is now a `file-format` property with a symbolic name
- for the file format. In some cases this might be a comma separated list
- of various different extensions due to libavformat idiosyncrasies.
-- (Client API) There is now a `mouse` command for generating mouse events
- over the video window.
-- (Client API) There is now a `partially-seekable` property that indicates
- whether a file is only considered `seekable` because of the stream cache,
- but would not be seekable otherwise (e.g. realtime stream, or HTTP with
- no resume support). Small relative seeks may be fine, but larger seeks
- will likely fail.
-- (Client API) There is now an MPV_EVENT_QUEUE_OVERFLOW event that is posted
- whenever events have to be dropped because the queue is full. No further
- events are posted until the MPV_EVENT_QUEUE_OVERFLOW is consumed to avoid
- duplicated posting.
-- (Client API) There is now a helper `mpv_wait_async_requests()` function
- that blocks until all known async requests have completed. (bug #1542)
-- (Client API) There is now a `detected-hwdec` property that returns the
- detected hardware decoder when one is successfully opened. This probably
- only returns a valid value after playback has started.
-- (Client API) There are now `audio_add`, `audio_remove` and `audio_reload`
- counterparts to the equivalent `sub_` commands for handling external audio
- files.
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.7.0..v0.8.0` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.7.0...v0.8.0 (only 250 commits).
-
-Release 0.7.3
-=============
-
-Changes
--------
-
-- Several documentation updates and typo fixes.
-- Don't show "0%" position when the stream is infinite / has unknown length.
-- Fixes the config file loading order so that lower priority `mpv.conf` files
- don't override higher priority `config` files.
-- (OSX) Remove coreaudio_exclusive from the "auto" codec list.
-- Failing to create a GL3 context is now a warning instead of error, to reduce
- user confusion.
-- The subtitle decoder now gets reset when cycling subtitles. This makes the
- currently shown subtitle event disappear even if cycling back to the current
- subtitle track.
-- `--shuffle` and `--merge-files` now affects the contents of playlist files,
- instead of just the list of files given as arguments to mpv.
-- `./waf install` avoids installing a few additional data/config files if only
- libmpv was built.
-- Improved channel mapping when the file's channel map doesn't match the AO's
- available channel maps.
-- (OSX) VDA now gracefully refuses to run on non-OpenGL3-capable systems instead
- of trying and failing to build its shaders.
-- Add workaround for vf_vapoursynth filters that expect an FPS.
-- The default value for `--screenshot-template` now shows up in
- `--list-options`.
-- `ao_pulse`'s `latency-hacks` suboption is now off by default, as it causes
- issues with newer pulseaudio releases. If A/V Sync issues happen, either
- add the `latency-hacks=on` pulse suboption in mpv.conf, or update the
- pulseaudio daemon (bug #1430).
-- (Linux) `ao_alsa` now deals better with audio device disconnection.
-- Client API: timestamp properties that have no value return "no" instead of
- magic number.
-
-Bug fixes
----------
-
-- (Windows, OSX) Many `ao_wasapi` and `ao_coreaudio` fixes.
-- (OSX) Make the window title update correctly on OS X Yosemite.
-- Fixes for handling mono audio on various AOs.
-- (Linux) Fixes resuming from suspend on ao_alsa.
-- Fixes for playlist file parsing.
-- Overly long options in the --list-options output now break the column layout
- instead of getting truncated; fixes shell completion scripts.
-- Fix rendering resolution of certain DVB subtitles (bug #1425).
-- Fix EDL or --merge-files breaking timestamps with .avi files.
-- Workaround for libavcodec bug with the VP9 codec parser (bug #1448),
- fixes possible crash.
-- Improve robustness of the matroska parser with broken files (bugs #1457,
- #1461).
-- Improve 10bit video compatibility with older GPUs (specially Intel) (bug
- #1383).
-- Fixes flashing the VO window when playing a list of files that includes
- non-media files (bug #1459).
-- Workaround for window embedding in OpenBox (bug #1235)
-- Fixes for several crashes and lockups (bugs #838, #1389, #1408, #1463, #1473,
- #1474).
-- Fixes for the zsh completion script (bugs #997)
-
-New features
-------------
-
-- mpv now prints the contents of its config.h when running in verbose mode, to
- aid with debugging.
-- The `include=` option now accepts `~` to refer to the HOME dir (bug #1406).
-- `af_volume` now prints ReplayGain values in verbose mode.
-- m3u playlists that don't have the `#EXTM3U` header are now accepted if they
- "look like" ASCII or UTF-8 text and have the .m3u extension.
-- Chapter marks in the seek bar now update when switching files.
-- Supports embedded cover art in MKV files (bug #1374).
-- (Windows) Video window can now be resized even with --no-border.
-- (Windows) Client API: the "run" command now works on Windows too.
-- (Linux) vo_wayland now supports key modifiers (Meta, Alt, Control, Shift)
-- Client API: "display-names" property is now observable.
-
-This listing is not complete. A complete changelog can be seen by running
-`git log v0.7.2..v0.7.3` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.7.2...v0.7.3.
-
-Release 0.7.2
-=============
-
-Changes
--------
-
-- Give precedence to the DVD menu navigation keyboard bindings so that user
- defined LEFT/RIGHT/... bindings don't break DVD menu navigation.
-- Try to fallback to the "default" device if the selected device is busy in the
- alsa AO.
-- Don't create Dock icon for audio only files on OS X.
-- Save screenshots to desktop when using the app bundle on OS X.
-- Restore ab-loop settings with playback resume.
-- Bump required youtube-dl version to 2014.11.26 and enable the ytdl_hook Lua
- script by default (now playing videos from YouTube and the like will work
- out of the box without any configuration change needed).
-
-Bug fixes
----------
-
-- Don't signal an error if --stream-dump is used.
-- Fix removing key bindings from Lua scripts.
-- Reject channel descriptions with too many channels in the coreaudio AO.
-- Don't async redraw when waiting for VO redraw on OS X (this fixes the very
- annoying glitch where the black bars disappear for a single frame when going
- fullscreen).
-- Fix mono playback with the also AO.
-- Don't crash if framebuffers are not available in the opengl VO.
-
-New features
-------------
-
-- Try to handle multi-arc videos in the ytdl_hook Lua script.
-
-This listing is not complete. There are many more bug fixes and changes. The
-complete change log can be viewed by running ``git log 47ec404..c7d6b21`` in
-the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/47ec404...c7d6b21.
-
-Release 0.7.1
-=============
-
-Changes
--------
-
-- Don't show the volume neutral marker on the OSD if softvol is disabled.
-- Don't select a subtitle track when executing the sub_add input command in
- "auto" mode.
-
-Bug fixes
----------
-
-- Fix busy loop when seeking while paused (this fixes a problem with pulseaudio
- that caused mpv and the pulseaudio daemon to use 100% CPU).
-- Fix Lua function utils.subprocess() in Windows versions older than Vista.
-- Avoid creating a window bigger than the screen on Windows.
-- Don't ignore the last line in m3u playlists.
-- Don't crash if a codec could not be opened.
-- Dynamically allocate audio channel map entries (this should fix a crash in
- the alsa and coreaudio AOs with audio devices that support more than 20
- channel maps).
-- Ignore the "srgb" option in the opengl VO if hardware decoding is enabled.
-- Linearize non-RGB sRGB files correctly (eg. JPEG).
-- Fix opening reference URLs (.file/id=) on OS X.
-
-This listing is not complete. There are many more bug fixes and changes. The
-complete change log can be viewed by running ``git log 8d8b36d..6583ad6`` in
-the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/8d8b36d...6583ad6.
-
-Release 0.7.0
-=============
-
-Changes
--------
-
-- Buffer partial log messages in the client API (the client API will now only
- pass full log messages to clients).
-- Remove ncurses/terminfo/termcap support (it was disabled by default and
- replaced by new code since v0.6.0).
-- Enable cdda:// support by default again (it was disabled since v0.6.0).
-- Cascade-load input.conf (if there are several input.confs in the set of valid
- config paths, load them all).
-- Draw the OSD twice in 3D mode (this fixes subtitles display in 3D mode).
-- Make wasapi the default AO on Windows again since many of its problems have
- been solved.
-- Use "site-functions" subdir to install the zsh completion script instead of
- the Debian-specific "vendor-completions" (also provide the --zshdir waf
- configure option for changing this value).
-- Improve synchronization between the Cocoa GUI and the player (this fixes some
- long standing deadlock issues on Mac OS X).
-- Remove --fs-missioncontrol option (only relevant to Mac OS X).
-
-New features
-------------
-
-- Enable pitch correction by default when playing at higher speeds (this can be
- controlled with the --audio-pitch-correction option).
-- Open stream and demuxer asynchronously (this should avoid having the player
- get blocked on network streams).
-- Add cache-buffering-state property for querying the cache fill status until
- the player unpauses.
-- Add support for listing and selecting the audio device (note that it is not
- implemented for all AOs, see the --audio-device option for more information).
-- Add support for a JSON-based IPC mechanism (note that this is not currently
- supported on Windows, see the JSON IPC section in the manpage for more
- information).
-- Add Lua utility function for starting processes (see utils.subprocess() in the
- manpage).
-- Add Lua utility function for parsing JSON (see utils.parse_json() in the
- manpage).
-- Add field-dominance property (see --field-dominance option).
-- Add video-rotate property (see --video-rotate option).
-- Add playback-abort property for querying whether playback is stopped or is to
- be stopped.
-- Add cursor-autohide property (see --cursor-autohide option).
-- Add vo-configured property for querying whether a window is created.
-- Add support for dxva2 hardware acceleration on Windows.
-- Drop libquvi support (this has been replaced by a built-in Lua script that
- invokes the youtube-dl tool, which needs to be installed, see the --ytdl
- option).
-- Add support for loading chapters from an external file (see the
- --chapters-file option).
-- Add window-minimized property for querying whether the window is minimized
- (works for X11 only).
-- Make it possible to configure the OSC seekbar style (see the "seekbarstyle"
- OSC option).
-- Add support for libmpv on Mac OS X (it used to be broken, now it's fully
- functional and also provides support for embedding the mpv window inside a
- Cocoa/Qt application).
-- Try to use the audio channel map reported by ALSA in the alsa AO.
-- Add option to disable text OSD rendering completely (useful for working around
- certain fontconfig issues, see the --use-text-osd option).
+https://github.com/mpv-player/mpv/compare/v0.27.0...v0.28.0 or
+https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.27.0..v0.28.0
-This listing is not complete. There are many more bug fixes and changes. The
-complete change log can be viewed by running ``git log 7759c18..9479daa`` in
-the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/7759c18...9479daa (only 250 commits).
diff --git a/TOOLS/appveyor-build.sh b/TOOLS/appveyor-build.sh
index f6c94fb..8796118 100755
--- a/TOOLS/appveyor-build.sh
+++ b/TOOLS/appveyor-build.sh
@@ -10,7 +10,10 @@ export PYTHON=/usr/bin/python3
"$PYTHON" bootstrap.py
"$PYTHON" waf configure \
--check-c-compiler=gcc \
- --disable-cdda \
+ --disable-egl-angle-lib \
+ --enable-crossc \
+ --enable-d3d-hwaccel \
+ --enable-d3d11 \
--enable-egl-angle \
--enable-jpeg \
--enable-lcms2 \
@@ -18,5 +21,7 @@ export PYTHON=/usr/bin/python3
--enable-libass \
--enable-lua \
--enable-rubberband \
- --enable-uchardet
+ --enable-shaderc \
+ --enable-uchardet \
+ --enable-vulkan
"$PYTHON" waf build
diff --git a/TOOLS/appveyor-install.sh b/TOOLS/appveyor-install.sh
index b0d276e..7c0a345 100755
--- a/TOOLS/appveyor-install.sh
+++ b/TOOLS/appveyor-install.sh
@@ -1,15 +1,6 @@
#!/usr/bin/bash
set -e
-case $MSYSTEM in
-MINGW32)
- export MINGW_PACKAGE_PREFIX=mingw-w64-i686
- ;;
-MINGW64)
- export MINGW_PACKAGE_PREFIX=mingw-w64-x86_64
- ;;
-esac
-
# Write an empty fonts.conf to speed up fc-cache
export FONTCONFIG_FILE=/dummy-fonts.conf
cat >"$FONTCONFIG_FILE" <<EOF
@@ -20,16 +11,62 @@ EOF
# Install build dependencies for mpv
pacman -S --noconfirm --needed \
- $MINGW_PACKAGE_PREFIX-gcc \
+ $MINGW_PACKAGE_PREFIX-toolchain \
$MINGW_PACKAGE_PREFIX-angleproject-git \
- $MINGW_PACKAGE_PREFIX-ffmpeg \
+ $MINGW_PACKAGE_PREFIX-cmake \
$MINGW_PACKAGE_PREFIX-lcms2 \
$MINGW_PACKAGE_PREFIX-libarchive \
$MINGW_PACKAGE_PREFIX-libass \
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
$MINGW_PACKAGE_PREFIX-lua51 \
+ $MINGW_PACKAGE_PREFIX-ninja \
$MINGW_PACKAGE_PREFIX-rubberband \
- $MINGW_PACKAGE_PREFIX-uchardet
+ $MINGW_PACKAGE_PREFIX-uchardet \
+ $MINGW_PACKAGE_PREFIX-vulkan
# Delete unused packages to reduce space used in the Appveyor cache
pacman -Sc --noconfirm
+
+# Compile ffmpeg
+(
+ git clone --depth=1 https://github.com/FFmpeg/ffmpeg.git && cd ffmpeg
+
+ mkdir build && cd build
+ ../configure \
+ --prefix=$MINGW_PREFIX \
+ --target-os=mingw32 \
+ --arch=$MSYSTEM_CARCH \
+ --disable-static \
+ --disable-doc \
+ --disable-asm \
+ --enable-gpl \
+ --enable-version3 \
+ --enable-shared \
+ --enable-pic \
+ --enable-d3d11va \
+ --enable-dxva2 \
+ --enable-schannel
+ make -j4 install
+)
+
+# Compile shaderc
+(
+ git clone --depth=1 https://github.com/google/shaderc && cd shaderc
+ git clone --depth=1 https://github.com/google/glslang.git third_party/glslang
+ git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Tools.git third_party/spirv-tools
+ git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git third_party/spirv-headers
+
+ mkdir build && cd build
+ cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
+ -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
+ ninja install
+ cp -f libshaderc/libshaderc_shared.dll $MINGW_PREFIX/bin/
+)
+
+# Compile crossc
+(
+ git clone --depth=1 https://github.com/rossy/crossc && cd crossc
+ git submodule update --init
+
+ make -j4 install prefix=$MINGW_PREFIX
+)
diff --git a/TOOLS/lua/audio-hotplug-test.lua b/TOOLS/lua/audio-hotplug-test.lua
index f27b793..8dedc68 100644
--- a/TOOLS/lua/audio-hotplug-test.lua
+++ b/TOOLS/lua/audio-hotplug-test.lua
@@ -6,8 +6,3 @@ mp.observe_property("audio-device-list", "native", function(name, val)
print(" - '" .. e.name .. "' (" .. e.description .. ")")
end
end)
-
-mp.observe_property("audio-out-detected-device", "native", function(name, val)
- print("Detected audio device changed:")
- print(" - '" .. val)
-end)
diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua
index 8802c5d..2c72a60 100644
--- a/TOOLS/lua/autoload.lua
+++ b/TOOLS/lua/autoload.lua
@@ -22,7 +22,7 @@ end
EXTENSIONS = Set {
'mkv', 'avi', 'mp4', 'ogv', 'webm', 'rmvb', 'flv', 'wmv', 'mpeg', 'mpg', 'm4v', '3gp',
- 'mp3', 'wav', 'ogv', 'flac', 'm4a', 'wma',
+ 'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
}
mputils = require 'mp.utils'
diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py
index e9ef8bc..ba0efd9 100755
--- a/TOOLS/osxbundle.py
+++ b/TOOLS/osxbundle.py
@@ -39,6 +39,10 @@ def apply_plist_template(plist_file, version):
for line in fileinput.input(plist_file, inplace=1):
print (line.rstrip().replace('${VERSION}', version))
+def create_bundle_symlink(binary_name, symlink_name):
+ os.symlink(os.path.basename(binary_name),
+ os.path.join(target_directory(binary_name), symlink_name))
+
def bundle_version():
if os.path.exists('VERSION'):
x = open('VERSION')
@@ -69,6 +73,8 @@ def main():
copy_bundle(binary_name)
print("> copying binary")
copy_binary(binary_name)
+ print("> create bundle symlink")
+ create_bundle_symlink(binary_name, "mpv-bundle")
print("> generating Info.plist")
apply_plist_template(target_plist(binary_name), version)
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Info.plist b/TOOLS/osxbundle/mpv.app/Contents/Info.plist
index 11e34c4..89ff4bf 100644
--- a/TOOLS/osxbundle/mpv.app/Contents/Info.plist
+++ b/TOOLS/osxbundle/mpv.app/Contents/Info.plist
@@ -173,7 +173,7 @@
</dict>
</array>
<key>CFBundleExecutable</key>
- <string>mpv-wrapper.sh</string>
+ <string>mpv-bundle</string>
<key>CFBundleIconFile</key>
<string>icon</string>
<key>CFBundleIdentifier</key>
diff --git a/TOOLS/osxbundle/mpv.app/Contents/MacOS/mpv-wrapper.sh b/TOOLS/osxbundle/mpv.app/Contents/MacOS/mpv-wrapper.sh
deleted file mode 100755
index f84c27a..0000000
--- a/TOOLS/osxbundle/mpv.app/Contents/MacOS/mpv-wrapper.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-export MPVBUNDLE="true"
-
-# set the right args for the user specified standard shell
-# to load the expected profiles and configs
-args="-c"
-case "$SHELL" in
- *bash) args="-l $args";;
- *zsh) args="-l -i $args";;
-esac
-
-cd "$(dirname "$0")"
-$SHELL $args "./mpv --player-operation-mode=pseudo-gui"
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf b/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
new file mode 100644
index 0000000..bdffa7a
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
@@ -0,0 +1 @@
+player-operation-mode=pseudo-gui
diff --git a/TOOLS/travis-deps b/TOOLS/travis-deps
index 8206c6b..aba68da 100755
--- a/TOOLS/travis-deps
+++ b/TOOLS/travis-deps
@@ -91,13 +91,9 @@ class Libav < TravisDepsBuilder
:action => :git,
:url => "git://git.libav.org/libav.git"
},
- "ffmpeg-stable" => {
- :action => :stable,
- :url => 'http://www.ffmpeg.org/releases/ffmpeg-3.2.2.tar.gz'
- },
"ffmpeg-git" => {
:action => :git,
- :url => "git://github.com/FFmpeg/FFmpeg.git"
+ :url => "https://github.com/FFmpeg/FFmpeg.git"
}
}
end
@@ -110,7 +106,7 @@ end
class LibavOsx < Libav
def build_map
{
- "ffmpeg-stable" => { :action => :package, :url => 'ffmpeg' },
+ "ffmpeg-git" => { :action => :package, :url => 'ffmpeg' },
}
end
end
diff --git a/VERSION b/VERSION
index 1b58cc1..697f087 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.27.0
+0.28.0
diff --git a/appveyor.yml b/appveyor.yml
index 4224adc..47a87c2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -19,9 +19,9 @@ install:
# Update non-core packages
- C:\msys64\usr\bin\pacman -Suu --noconfirm
# Install required MSYS2 packages
- - C:\msys64\usr\bin\pacman -S --noconfirm --needed perl python pkg-config
+ - C:\msys64\usr\bin\pacman -S --noconfirm --needed base-devel perl python pkg-config
# Now MSYS2 is up to date, do the rest of the install from a bash script
- - C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-install.sh"
+ - C:\msys64\usr\bin\bash -lc "cd && exec \"$APPVEYOR_BUILD_FOLDER\"/TOOLS/appveyor-install.sh"
build_script:
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-build.sh"
on_failure:
diff --git a/audio/aconverter.c b/audio/aconverter.c
new file mode 100644
index 0000000..19b8960
--- /dev/null
+++ b/audio/aconverter.c
@@ -0,0 +1,641 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libavutil/opt.h>
+#include <libavutil/common.h>
+#include <libavutil/samplefmt.h>
+#include <libavutil/channel_layout.h>
+#include <libavutil/mathematics.h>
+
+#include "config.h"
+
+#include "common/common.h"
+#include "common/av_common.h"
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "options/m_option.h"
+#include "aconverter.h"
+#include "aframe.h"
+#include "fmt-conversion.h"
+#include "format.h"
+
+#define HAVE_LIBSWRESAMPLE (!HAVE_LIBAV)
+#define HAVE_LIBAVRESAMPLE HAVE_LIBAV
+
+#if HAVE_LIBAVRESAMPLE
+#include <libavresample/avresample.h>
+#elif HAVE_LIBSWRESAMPLE
+#include <libswresample/swresample.h>
+#define AVAudioResampleContext SwrContext
+#define avresample_alloc_context swr_alloc
+#define avresample_open swr_init
+#define avresample_close(x) do { } while(0)
+#define avresample_free swr_free
+#define avresample_available(x) 0
+#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
+ swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
+#define avresample_set_channel_mapping swr_set_channel_mapping
+#define avresample_set_compensation swr_set_compensation
+#else
+#error "config.h broken or no resampler found"
+#endif
+
+struct mp_aconverter {
+ struct mp_log *log;
+ struct mpv_global *global;
+ double playback_speed;
+ bool is_resampling;
+ bool passthrough_mode;
+ struct AVAudioResampleContext *avrctx;
+ struct mp_aframe *avrctx_fmt; // output format of avrctx
+ struct mp_aframe *pool_fmt; // format used to allocate frames for avrctx output
+ struct mp_aframe *pre_out_fmt; // format before final conversion
+ struct AVAudioResampleContext *avrctx_out; // for output channel reordering
+ const struct mp_resample_opts *opts; // opts requested by the user
+ // At least libswresample keeps a pointer around for this:
+ int reorder_in[MP_NUM_CHANNELS];
+ int reorder_out[MP_NUM_CHANNELS];
+ struct mp_aframe_pool *reorder_buffer;
+ struct mp_aframe_pool *out_pool;
+
+ int in_rate_user; // user input sample rate
+ int in_rate; // actual rate (used by lavr), adjusted for playback speed
+ int in_format;
+ struct mp_chmap in_channels;
+ int out_rate;
+ int out_format;
+ struct mp_chmap out_channels;
+
+ struct mp_aframe *input; // queued input frame
+ bool input_eof; // queued input EOF
+ struct mp_aframe *output; // queued output frame
+ bool output_eof; // queued output EOF
+};
+
+#if HAVE_LIBAVRESAMPLE
+static double get_delay(struct mp_aconverter *p)
+{
+ return avresample_get_delay(p->avrctx) / (double)p->in_rate +
+ avresample_available(p->avrctx) / (double)p->out_rate;
+}
+static int get_out_samples(struct mp_aconverter *p, int in_samples)
+{
+ return avresample_get_out_samples(p->avrctx, in_samples);
+}
+#else
+static double get_delay(struct mp_aconverter *p)
+{
+ int64_t base = p->in_rate * (int64_t)p->out_rate;
+ return swr_get_delay(p->avrctx, base) / (double)base;
+}
+static int get_out_samples(struct mp_aconverter *p, int in_samples)
+{
+ return swr_get_out_samples(p->avrctx, in_samples);
+}
+#endif
+
+static void close_lavrr(struct mp_aconverter *p)
+{
+ if (p->avrctx)
+ avresample_close(p->avrctx);
+ avresample_free(&p->avrctx);
+ if (p->avrctx_out)
+ avresample_close(p->avrctx_out);
+ avresample_free(&p->avrctx_out);
+
+ TA_FREEP(&p->pre_out_fmt);
+ TA_FREEP(&p->avrctx_fmt);
+ TA_FREEP(&p->pool_fmt);
+}
+
+static int rate_from_speed(int rate, double speed)
+{
+ return lrint(rate * speed);
+}
+
+static struct mp_chmap fudge_pairs[][2] = {
+ {MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
+ {MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
+ {MP_CHMAP2(SDL, SDR), MP_CHMAP2(SL, SR)},
+ {MP_CHMAP2(SL, SR), MP_CHMAP2(SDL, SDR)},
+};
+
+// Modify out_layout and return the new value. The intention is reducing the
+// loss libswresample's rematrixing will cause by exchanging similar, but
+// strictly speaking incompatible channel pairs. For example, 7.1 should be
+// changed to 7.1(wide) without dropping the SL/SR channels. (We still leave
+// it to libswresample to create the remix matrix.)
+static uint64_t fudge_layout_conversion(struct mp_aconverter *p,
+ uint64_t in, uint64_t out)
+{
+ for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
+ uint64_t a = mp_chmap_to_lavc(&fudge_pairs[n][0]);
+ uint64_t b = mp_chmap_to_lavc(&fudge_pairs[n][1]);
+ if ((in & a) == a && (in & b) == 0 &&
+ (out & a) == 0 && (out & b) == b)
+ {
+ out = (out & ~b) | a;
+
+ MP_VERBOSE(p, "Fudge: %s -> %s\n",
+ mp_chmap_to_str(&fudge_pairs[n][0]),
+ mp_chmap_to_str(&fudge_pairs[n][1]));
+ }
+ }
+ return out;
+}
+
+// mp_chmap_get_reorder() performs:
+// to->speaker[n] = from->speaker[src[n]]
+// but libavresample does:
+// to->speaker[dst[n]] = from->speaker[n]
+static void transpose_order(int *map, int num)
+{
+ int nmap[MP_NUM_CHANNELS] = {0};
+ for (int n = 0; n < num; n++) {
+ for (int i = 0; i < num; i++) {
+ if (map[n] == i)
+ nmap[i] = n;
+ }
+ }
+ memcpy(map, nmap, sizeof(nmap));
+}
+
+static bool configure_lavrr(struct mp_aconverter *p, bool verbose)
+{
+ close_lavrr(p);
+
+ p->in_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
+
+ p->passthrough_mode = p->opts->allow_passthrough &&
+ p->in_rate == p->out_rate &&
+ p->in_format == p->out_format &&
+ mp_chmap_equals(&p->in_channels, &p->out_channels);
+
+ if (p->passthrough_mode)
+ return true;
+
+ p->avrctx = avresample_alloc_context();
+ p->avrctx_out = avresample_alloc_context();
+ if (!p->avrctx || !p->avrctx_out)
+ goto error;
+
+ enum AVSampleFormat in_samplefmt = af_to_avformat(p->in_format);
+ enum AVSampleFormat out_samplefmt = af_to_avformat(p->out_format);
+ enum AVSampleFormat out_samplefmtp = av_get_planar_sample_fmt(out_samplefmt);
+
+ if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
+ out_samplefmt == AV_SAMPLE_FMT_NONE ||
+ out_samplefmtp == AV_SAMPLE_FMT_NONE)
+ goto error;
+
+ av_opt_set_int(p->avrctx, "filter_size", p->opts->filter_size, 0);
+ av_opt_set_int(p->avrctx, "phase_shift", p->opts->phase_shift, 0);
+ av_opt_set_int(p->avrctx, "linear_interp", p->opts->linear, 0);
+
+ double cutoff = p->opts->cutoff;
+ if (cutoff <= 0.0)
+ cutoff = MPMAX(1.0 - 6.5 / (p->opts->filter_size + 8), 0.80);
+ av_opt_set_double(p->avrctx, "cutoff", cutoff, 0);
+
+ int global_normalize;
+ mp_read_option_raw(p->global, "audio-normalize-downmix", &m_option_type_flag,
+ &global_normalize);
+ int normalize = p->opts->normalize;
+ if (normalize < 0)
+ normalize = global_normalize;
+#if HAVE_LIBSWRESAMPLE
+ av_opt_set_double(p->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
+#else
+ av_opt_set_int(p->avrctx, "normalize_mix_level", !!normalize, 0);
+#endif
+
+ if (mp_set_avopts(p->log, p->avrctx, p->opts->avopts) < 0)
+ goto error;
+
+ struct mp_chmap map_in = p->in_channels;
+ struct mp_chmap map_out = p->out_channels;
+
+ // Try not to do any remixing if at least one is "unknown". Some corner
+ // cases also benefit from disabling all channel handling logic if the
+ // src/dst layouts are the same (like fl-fr-na -> fl-fr-na).
+ if (mp_chmap_is_unknown(&map_in) || mp_chmap_is_unknown(&map_out) ||
+ mp_chmap_equals(&map_in, &map_out))
+ {
+ mp_chmap_set_unknown(&map_in, map_in.num);
+ mp_chmap_set_unknown(&map_out, map_out.num);
+ }
+
+ // unchecked: don't take any channel reordering into account
+ uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
+ uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
+
+ struct mp_chmap in_lavc, out_lavc;
+ mp_chmap_from_lavc(&in_lavc, in_ch_layout);
+ mp_chmap_from_lavc(&out_lavc, out_ch_layout);
+
+ if (verbose && !mp_chmap_equals(&in_lavc, &out_lavc)) {
+ MP_VERBOSE(p, "Remix: %s -> %s\n", mp_chmap_to_str(&in_lavc),
+ mp_chmap_to_str(&out_lavc));
+ }
+
+ if (in_lavc.num != map_in.num) {
+ // For handling NA channels, we would have to add a planarization step.
+ MP_FATAL(p, "Unsupported input channel layout %s.\n",
+ mp_chmap_to_str(&map_in));
+ goto error;
+ }
+
+ mp_chmap_get_reorder(p->reorder_in, &map_in, &in_lavc);
+ transpose_order(p->reorder_in, map_in.num);
+
+ if (mp_chmap_equals(&out_lavc, &map_out)) {
+ // No intermediate step required - output new format directly.
+ out_samplefmtp = out_samplefmt;
+ } else {
+ // Verify that we really just reorder and/or insert NA channels.
+ struct mp_chmap withna = out_lavc;
+ mp_chmap_fill_na(&withna, map_out.num);
+ if (withna.num != map_out.num)
+ goto error;
+ }
+ mp_chmap_get_reorder(p->reorder_out, &out_lavc, &map_out);
+
+ p->pre_out_fmt = mp_aframe_create();
+ mp_aframe_set_rate(p->pre_out_fmt, p->out_rate);
+ mp_aframe_set_chmap(p->pre_out_fmt, &p->out_channels);
+ mp_aframe_set_format(p->pre_out_fmt, p->out_format);
+
+ p->avrctx_fmt = mp_aframe_create();
+ mp_aframe_config_copy(p->avrctx_fmt, p->pre_out_fmt);
+ mp_aframe_set_chmap(p->avrctx_fmt, &out_lavc);
+ mp_aframe_set_format(p->avrctx_fmt, af_from_avformat(out_samplefmtp));
+
+ // If there are NA channels, the final output will have more channels than
+ // the avrctx output. Also, avrctx will output planar (out_samplefmtp was
+ // not overwritten). Allocate the output frame with more channels, so the
+ // NA channels can be trivially added.
+ p->pool_fmt = mp_aframe_create();
+ mp_aframe_config_copy(p->pool_fmt, p->avrctx_fmt);
+ if (map_out.num > out_lavc.num)
+ mp_aframe_set_chmap(p->pool_fmt, &map_out);
+
+ out_ch_layout = fudge_layout_conversion(p, in_ch_layout, out_ch_layout);
+
+ // Real conversion; output is input to avrctx_out.
+ av_opt_set_int(p->avrctx, "in_channel_layout", in_ch_layout, 0);
+ av_opt_set_int(p->avrctx, "out_channel_layout", out_ch_layout, 0);
+ av_opt_set_int(p->avrctx, "in_sample_rate", p->in_rate, 0);
+ av_opt_set_int(p->avrctx, "out_sample_rate", p->out_rate, 0);
+ av_opt_set_int(p->avrctx, "in_sample_fmt", in_samplefmt, 0);
+ av_opt_set_int(p->avrctx, "out_sample_fmt", out_samplefmtp, 0);
+
+ // Just needs the correct number of channels for deplanarization.
+ struct mp_chmap fake_chmap;
+ mp_chmap_set_unknown(&fake_chmap, map_out.num);
+ uint64_t fake_out_ch_layout = mp_chmap_to_lavc_unchecked(&fake_chmap);
+ if (!fake_out_ch_layout)
+ goto error;
+ av_opt_set_int(p->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
+ av_opt_set_int(p->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
+
+ av_opt_set_int(p->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
+ av_opt_set_int(p->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
+ av_opt_set_int(p->avrctx_out, "in_sample_rate", p->out_rate, 0);
+ av_opt_set_int(p->avrctx_out, "out_sample_rate", p->out_rate, 0);
+
+ // API has weird requirements, quoting avresample.h:
+ // * This function can only be called when the allocated context is not open.
+ // * Also, the input channel layout must have already been set.
+ avresample_set_channel_mapping(p->avrctx, p->reorder_in);
+
+ p->is_resampling = false;
+
+ if (avresample_open(p->avrctx) < 0 || avresample_open(p->avrctx_out) < 0) {
+ MP_ERR(p, "Cannot open Libavresample context.\n");
+ goto error;
+ }
+ return true;
+
+error:
+ close_lavrr(p);
+ return false;
+}
+
+bool mp_aconverter_reconfig(struct mp_aconverter *p,
+ int in_rate, int in_format, struct mp_chmap in_channels,
+ int out_rate, int out_format, struct mp_chmap out_channels)
+{
+ close_lavrr(p);
+
+ TA_FREEP(&p->input);
+ TA_FREEP(&p->output);
+ p->input_eof = p->output_eof = false;
+
+ p->playback_speed = 1.0;
+
+ p->in_rate_user = in_rate;
+ p->in_format = in_format;
+ p->in_channels = in_channels;
+ p->out_rate = out_rate;
+ p->out_format = out_format;
+ p->out_channels = out_channels;
+
+ return configure_lavrr(p, true);
+}
+
+void mp_aconverter_flush(struct mp_aconverter *p)
+{
+ if (!p->avrctx)
+ return;
+#if HAVE_LIBSWRESAMPLE
+ swr_close(p->avrctx);
+ if (swr_init(p->avrctx) < 0)
+ close_lavrr(p);
+#else
+ while (avresample_read(p->avrctx, NULL, 1000) > 0) {}
+#endif
+}
+
+void mp_aconverter_set_speed(struct mp_aconverter *p, double speed)
+{
+ p->playback_speed = speed;
+}
+
+static void extra_output_conversion(struct mp_aframe *mpa)
+{
+ int format = af_fmt_from_planar(mp_aframe_get_format(mpa));
+ int num_planes = mp_aframe_get_planes(mpa);
+ uint8_t **planes = mp_aframe_get_data_rw(mpa);
+ if (!planes)
+ return;
+ for (int p = 0; p < num_planes; p++) {
+ void *ptr = planes[p];
+ int total = mp_aframe_get_total_plane_samples(mpa);
+ if (format == AF_FORMAT_FLOAT) {
+ for (int s = 0; s < total; s++)
+ ((float *)ptr)[s] = av_clipf(((float *)ptr)[s], -1.0f, 1.0f);
+ } else if (format == AF_FORMAT_DOUBLE) {
+ for (int s = 0; s < total; s++)
+ ((double *)ptr)[s] = MPCLAMP(((double *)ptr)[s], -1.0, 1.0);
+ }
+ }
+}
+
+// This relies on the tricky way mpa was allocated.
+static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
+ struct mp_chmap *newmap)
+{
+ if (!mp_aframe_set_chmap(mpa, newmap))
+ return false;
+
+ int num_planes = newmap->num;
+ uint8_t **planes = mp_aframe_get_data_rw(mpa);
+ uint8_t *old_planes[MP_NUM_CHANNELS];
+ assert(num_planes <= MP_NUM_CHANNELS);
+ for (int n = 0; n < num_planes; n++)
+ old_planes[n] = planes[n];
+
+ int next_na = 0;
+ for (int n = 0; n < num_planes; n++)
+ next_na += newmap->speaker[n] != MP_SPEAKER_ID_NA;
+
+ for (int n = 0; n < num_planes; n++) {
+ int src = reorder[n];
+ assert(src >= -1 && src < num_planes);
+ if (src >= 0) {
+ planes[n] = old_planes[src];
+ } else {
+ assert(next_na < num_planes);
+ planes[n] = old_planes[next_na++];
+ // The NA planes were never written by avrctx, so clear them.
+ af_fill_silence(planes[n],
+ mp_aframe_get_sstride(mpa) * mp_aframe_get_size(mpa),
+ mp_aframe_get_format(mpa));
+ }
+ }
+
+ return true;
+}
+
+static int resample_frame(struct AVAudioResampleContext *r,
+ struct mp_aframe *out, struct mp_aframe *in)
+{
+ // Be aware that the channel layout and count can be different for in and
+ // out frames. In some situations the caller will fix up the frames before
+ // or after conversion. The sample rates can also be different.
+ AVFrame *av_i = in ? mp_aframe_get_raw_avframe(in) : NULL;
+ AVFrame *av_o = out ? mp_aframe_get_raw_avframe(out) : NULL;
+ return avresample_convert(r,
+ av_o ? av_o->extended_data : NULL,
+ av_o ? av_o->linesize[0] : 0,
+ av_o ? av_o->nb_samples : 0,
+ av_i ? av_i->extended_data : NULL,
+ av_i ? av_i->linesize[0] : 0,
+ av_i ? av_i->nb_samples : 0);
+}
+
+static void filter_resample(struct mp_aconverter *p, struct mp_aframe *in)
+{
+ struct mp_aframe *out = NULL;
+
+ if (!p->avrctx)
+ goto error;
+
+ int samples = get_out_samples(p, in ? mp_aframe_get_size(in) : 0);
+ out = mp_aframe_create();
+ mp_aframe_config_copy(out, p->pool_fmt);
+ if (mp_aframe_pool_allocate(p->out_pool, out, samples) < 0)
+ goto error;
+
+ int out_samples = 0;
+ if (samples) {
+ out_samples = resample_frame(p->avrctx, out, in);
+ if (out_samples < 0 || out_samples > samples)
+ goto error;
+ mp_aframe_set_size(out, out_samples);
+ }
+
+ struct mp_chmap out_chmap;
+ if (!mp_aframe_get_chmap(p->pool_fmt, &out_chmap))
+ goto error;
+ if (!reorder_planes(out, p->reorder_out, &out_chmap))
+ goto error;
+
+ if (!mp_aframe_config_equals(out, p->pre_out_fmt)) {
+ struct mp_aframe *new = mp_aframe_create();
+ mp_aframe_config_copy(new, p->pre_out_fmt);
+ if (mp_aframe_pool_allocate(p->reorder_buffer, new, out_samples) < 0) {
+ talloc_free(new);
+ goto error;
+ }
+ int got = 0;
+ if (out_samples)
+ got = resample_frame(p->avrctx_out, new, out);
+ talloc_free(out);
+ out = new;
+ if (got != out_samples)
+ goto error;
+ }
+
+ extra_output_conversion(out);
+
+ if (in)
+ mp_aframe_copy_attributes(out, in);
+
+ if (out_samples) {
+ p->output = out;
+ } else {
+ talloc_free(out);
+ }
+ p->output_eof = !in; // we've read everything
+
+ return;
+error:
+ talloc_free(out);
+ MP_ERR(p, "Error on resampling.\n");
+}
+
+static void filter(struct mp_aconverter *p)
+{
+ if (p->output || p->output_eof || !(p->input || p->input_eof))
+ return;
+
+ int new_rate = rate_from_speed(p->in_rate_user, p->playback_speed);
+
+ if (p->passthrough_mode && new_rate != p->in_rate)
+ configure_lavrr(p, false);
+
+ if (p->passthrough_mode) {
+ p->output = p->input;
+ p->input = NULL;
+ p->output_eof = p->input_eof;
+ p->input_eof = false;
+ return;
+ }
+
+ if (p->avrctx && !(!p->is_resampling && new_rate == p->in_rate)) {
+ AVRational r = av_d2q(p->playback_speed * p->in_rate_user / p->in_rate,
+ INT_MAX / 2);
+ // Essentially, swr/avresample_set_compensation() does 2 things:
+ // - adjust output sample rate by sample_delta/compensation_distance
+ // - reset the adjustment after compensation_distance output samples
+ // Increase the compensation_distance to avoid undesired reset
+ // semantics - we want to keep the ratio for the whole frame we're
+ // feeding it, until the next filter() call.
+ int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1);
+ r = (AVRational){ r.num * mult, r.den * mult };
+ if (avresample_set_compensation(p->avrctx, r.den - r.num, r.den) >= 0) {
+ new_rate = p->in_rate;
+ p->is_resampling = true;
+ }
+ }
+
+ bool need_reinit = fabs(new_rate / (double)p->in_rate - 1) > 0.01;
+ if (need_reinit && new_rate != p->in_rate) {
+ // Before reconfiguring, drain the audio that is still buffered
+ // in the resampler.
+ filter_resample(p, NULL);
+ // Reinitialize resampler.
+ configure_lavrr(p, false);
+ p->output_eof = false;
+ if (p->output)
+ return; // need to read output before continuing filtering
+ }
+
+ filter_resample(p, p->input);
+ TA_FREEP(&p->input);
+ p->input_eof = false;
+}
+
+// Queue input. If true, ownership of in passes to mp_aconverted and the input
+// was accepted. Otherwise, return false and reject in.
+// in==NULL means trigger EOF.
+bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in)
+{
+ if (p->input || p->input_eof)
+ return false;
+
+ p->input = in;
+ p->input_eof = !in;
+ return true;
+}
+
+// Return output frame, or NULL if nothing available.
+// *eof is set to true if NULL is returned, and it was due to EOF.
+struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof)
+{
+ *eof = false;
+
+ filter(p);
+
+ if (p->output) {
+ struct mp_aframe *out = p->output;
+ p->output = NULL;
+ return out;
+ }
+
+ *eof = p->output_eof;
+ p->output_eof = false;
+ return NULL;
+}
+
+double mp_aconverter_get_latency(struct mp_aconverter *p)
+{
+ double delay = get_delay(p);
+
+ if (p->input)
+ delay += mp_aframe_duration(p->input);
+
+ // In theory this is influenced by playback speed, but other parts of the
+ // player get it wrong anyway.
+ if (p->output)
+ delay += mp_aframe_duration(p->output);
+
+ return delay;
+}
+
+static void destroy_aconverter(void *ptr)
+{
+ struct mp_aconverter *p = ptr;
+
+ close_lavrr(p);
+
+ talloc_free(p->input);
+ talloc_free(p->output);
+}
+
+// If opts is not NULL, the pointer must be valid for the lifetime of the
+// mp_aconverter.
+struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
+ struct mp_log *log,
+ const struct mp_resample_opts *opts)
+{
+ struct mp_aconverter *p = talloc_zero(NULL, struct mp_aconverter);
+ p->log = log;
+ p->global = global;
+
+ static const struct mp_resample_opts defs = MP_RESAMPLE_OPTS_DEF;
+
+ p->opts = opts ? opts : &defs;
+
+ p->reorder_buffer = mp_aframe_pool_create(p);
+ p->out_pool = mp_aframe_pool_create(p);
+
+ talloc_set_destructor(p, destroy_aconverter);
+
+ return p;
+}
diff --git a/audio/aconverter.h b/audio/aconverter.h
new file mode 100644
index 0000000..57c5524
--- /dev/null
+++ b/audio/aconverter.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "chmap.h"
+
+struct mp_aconverter;
+struct mp_aframe;
+struct mpv_global;
+struct mp_log;
+
+struct mp_resample_opts {
+ int filter_size;
+ int phase_shift;
+ int linear;
+ double cutoff;
+ int normalize;
+ int allow_passthrough;
+ char **avopts;
+};
+
+#define MP_RESAMPLE_OPTS_DEF { \
+ .filter_size = 16, \
+ .cutoff = 0.0, \
+ .phase_shift = 10, \
+ .normalize = -1, \
+ }
+
+struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
+ struct mp_log *log,
+ const struct mp_resample_opts *opts);
+bool mp_aconverter_reconfig(struct mp_aconverter *p,
+ int in_rate, int in_format, struct mp_chmap in_channels,
+ int out_rate, int out_format, struct mp_chmap out_channels);
+void mp_aconverter_flush(struct mp_aconverter *p);
+void mp_aconverter_set_speed(struct mp_aconverter *p, double speed);
+bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in);
+struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof);
+double mp_aconverter_get_latency(struct mp_aconverter *p);
diff --git a/audio/aframe.c b/audio/aframe.c
index cf785f6..1f053a6 100644
--- a/audio/aframe.c
+++ b/audio/aframe.c
@@ -166,10 +166,10 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
dst->chmap = src->chmap;
dst->format = src->format;
- dst->pts = src->pts;
- if (av_frame_copy_props(dst->av_frame, src->av_frame) < 0)
- abort();
+ mp_aframe_copy_attributes(dst, src);
+
+ dst->av_frame->sample_rate = src->av_frame->sample_rate;
dst->av_frame->format = src->av_frame->format;
dst->av_frame->channel_layout = src->av_frame->channel_layout;
#if LIBAVUTIL_VERSION_MICRO >= 100
@@ -178,6 +178,20 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
#endif
}
+// Copy "soft" attributes from src to dst, excluding things which affect
+// frame allocation and organization.
+void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src)
+{
+ dst->pts = src->pts;
+
+ int rate = dst->av_frame->sample_rate;
+
+ if (av_frame_copy_props(dst->av_frame, src->av_frame) < 0)
+ abort();
+
+ dst->av_frame->sample_rate = rate;
+}
+
// Return whether a and b use the same physical audio format. Extra metadata
// such as PTS, per-frame signalling, and AVFrame side data is not compared.
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b)
@@ -283,7 +297,7 @@ bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in)
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate)
{
- if (rate < 1 && rate > 10000000)
+ if (rate < 1 || rate > 10000000)
return false;
frame->av_frame->sample_rate = rate;
return true;
@@ -317,6 +331,14 @@ size_t mp_aframe_get_sstride(struct mp_aframe *frame)
(af_fmt_is_planar(format) ? 1 : mp_aframe_get_channels(frame));
}
+// Return total number of samples on each plane.
+int mp_aframe_get_total_plane_samples(struct mp_aframe *frame)
+{
+ return frame->av_frame->nb_samples *
+ (af_fmt_is_planar(mp_aframe_get_format(frame))
+ ? 1 : mp_aframe_get_channels(frame));
+}
+
// Set data to the audio after the given number of samples (i.e. slice it).
void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
{
diff --git a/audio/aframe.h b/audio/aframe.h
index 5661178..9ce19cd 100644
--- a/audio/aframe.h
+++ b/audio/aframe.h
@@ -23,6 +23,8 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src);
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b);
bool mp_aframe_config_is_valid(struct mp_aframe *frame);
+void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src);
+
uint8_t **mp_aframe_get_data_ro(struct mp_aframe *frame);
uint8_t **mp_aframe_get_data_rw(struct mp_aframe *frame);
@@ -40,6 +42,7 @@ bool mp_aframe_set_size(struct mp_aframe *frame, int samples);
void mp_aframe_set_pts(struct mp_aframe *frame, double pts);
int mp_aframe_get_planes(struct mp_aframe *frame);
+int mp_aframe_get_total_plane_samples(struct mp_aframe *frame);
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
void mp_aframe_skip_samples(struct mp_aframe *f, int samples);
diff --git a/audio/audio.c b/audio/audio.c
index 008aa18..55e4266 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -358,8 +358,8 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
#if LIBAVUTIL_VERSION_MICRO >= 100
// FFmpeg being stupid POS again
- if (lavc_chmap.num != av_frame_get_channels(avframe))
- mp_chmap_from_channels(&lavc_chmap, av_frame_get_channels(avframe));
+ if (lavc_chmap.num != avframe->channels)
+ mp_chmap_from_channels(&lavc_chmap, avframe->channels);
#endif
new->rate = avframe->sample_rate;
@@ -406,6 +406,9 @@ fail:
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe)
{
+ if (!aframe)
+ return NULL;
+
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
struct mp_audio *res = mp_audio_from_avframe(av);
if (!res)
@@ -428,6 +431,34 @@ void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src)
dst->rate = mp_aframe_get_rate(src);
}
+struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa)
+{
+ if (!mpa)
+ return NULL;
+
+ struct mp_aframe *aframe = mp_aframe_create();
+ struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
+ mp_aframe_set_format(aframe, mpa->format);
+ mp_aframe_set_chmap(aframe, &mpa->channels);
+ mp_aframe_set_rate(aframe, mpa->rate);
+
+ // bullshit it into ffmpeg-compatible parameters
+ struct mp_audio mpb = *mpa;
+ struct mp_chmap chmap;
+ mp_chmap_set_unknown(&chmap, mpb.channels.num);
+ mp_audio_set_channels(&mpb, &chmap);
+ if (af_fmt_is_spdif(mpb.format))
+ mp_audio_set_format(&mpb, AF_FORMAT_S16);
+
+ // put the reference into av, which magically puts it into aframe
+ // aframe keeps its parameters, so the bullshit doesn't matter
+ if (mp_audio_to_avframe(&mpb, av) < 0) {
+ talloc_free(aframe);
+ return NULL;
+ }
+ return aframe;
+}
+
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
{
av_frame_unref(avframe);
@@ -442,7 +473,7 @@ int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
goto fail;
#if LIBAVUTIL_VERSION_MICRO >= 100
// FFmpeg being a stupid POS again
- av_frame_set_channels(avframe, frame->channels.num);
+ avframe->channels = frame->channels.num;
#endif
avframe->sample_rate = frame->rate;
diff --git a/audio/audio.h b/audio/audio.h
index a8370a0..f370067 100644
--- a/audio/audio.h
+++ b/audio/audio.h
@@ -86,6 +86,7 @@ int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
struct mp_aframe;
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe);
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src);
+struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa);
struct mp_audio_pool;
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
@@ -96,4 +97,6 @@ struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
struct mp_audio *frame);
+#include "filter/af.h"
+
#endif
diff --git a/audio/audio_buffer.c b/audio/audio_buffer.c
index a443a21..b54f1f4 100644
--- a/audio/audio_buffer.c
+++ b/audio/audio_buffer.c
@@ -21,141 +21,137 @@
#include "common/common.h"
+#include "chmap.h"
#include "audio_buffer.h"
-#include "audio.h"
#include "format.h"
struct mp_audio_buffer {
- struct mp_audio *buffer;
+ int format;
+ struct mp_chmap channels;
+ int srate;
+ int sstride;
+ int num_planes;
+ uint8_t *data[MP_NUM_CHANNELS];
+ int allocated;
+ int num_samples;
};
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx)
{
- struct mp_audio_buffer *ab = talloc(talloc_ctx, struct mp_audio_buffer);
- *ab = (struct mp_audio_buffer) {
- .buffer = talloc_zero(ab, struct mp_audio),
- };
- return ab;
+ return talloc_zero(talloc_ctx, struct mp_audio_buffer);
}
// Reinitialize the buffer, set a new format, drop old data.
// The audio data in fmt is not used, only the format.
-void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt)
-{
- mp_audio_copy_config(ab->buffer, fmt);
- mp_audio_realloc(ab->buffer, 1);
- ab->buffer->samples = 0;
-}
-
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
const struct mp_chmap *channels, int srate)
{
- struct mp_audio mpa = {0};
- mp_audio_set_format(&mpa, format);
- mp_audio_set_channels(&mpa, channels);
- mpa.rate = srate;
- mp_audio_buffer_reinit(ab, &mpa);
-}
-
-void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
- struct mp_audio *out_fmt)
-{
- *out_fmt = (struct mp_audio){0};
- mp_audio_copy_config(out_fmt, ab->buffer);
+ for (int n = 0; n < MP_NUM_CHANNELS; n++)
+ TA_FREEP(&ab->data[n]);
+ ab->format = format;
+ ab->channels = *channels;
+ ab->srate = srate;
+ ab->allocated = 0;
+ ab->num_samples = 0;
+ ab->sstride = af_fmt_to_bytes(ab->format);
+ ab->num_planes = 1;
+ if (af_fmt_is_planar(ab->format)) {
+ ab->num_planes = ab->channels.num;
+ } else {
+ ab->sstride *= ab->channels.num;
+ }
}
// Make the total size of the internal buffer at least this number of samples.
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples)
{
- mp_audio_realloc_min(ab->buffer, samples);
+ if (samples > ab->allocated) {
+ for (int n = 0; n < ab->num_planes; n++) {
+ ab->data[n] = talloc_realloc(ab, ab->data[n], char,
+ ab->sstride * samples);
+ }
+ ab->allocated = samples;
+ }
}
// Get number of samples that can be written without forcing a resize of the
// internal buffer.
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab)
{
- return mp_audio_get_allocated_size(ab->buffer) - ab->buffer->samples;
-}
-
-// Get a pointer to the end of the buffer (where writing would append). If the
-// internal buffer is too small for the given number of samples, it's resized.
-// After writing to the buffer, mp_audio_buffer_finish_write() has to be used
-// to make the written data part of the readable buffer.
-void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int samples,
- struct mp_audio *out_buffer)
-{
- assert(samples >= 0);
- mp_audio_realloc_min(ab->buffer, ab->buffer->samples + samples);
- *out_buffer = *ab->buffer;
- out_buffer->samples = ab->buffer->samples + samples;
- mp_audio_skip_samples(out_buffer, ab->buffer->samples);
+ return ab->allocated - ab->num_samples;
}
-void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples)
+// All integer parameters are in samples.
+// dst and src can overlap.
+static void copy_planes(struct mp_audio_buffer *ab,
+ uint8_t **dst, int dst_offset,
+ uint8_t **src, int src_offset, int length)
{
- assert(samples >= 0 && samples <= mp_audio_buffer_get_write_available(ab));
- ab->buffer->samples += samples;
+ for (int n = 0; n < ab->num_planes; n++) {
+ memmove((char *)dst[n] + dst_offset * ab->sstride,
+ (char *)src[n] + src_offset * ab->sstride,
+ length * ab->sstride);
+ }
}
// Append data to the end of the buffer.
// If the buffer is not large enough, it is transparently resized.
-// For now always copies the data.
-void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa)
+void mp_audio_buffer_append(struct mp_audio_buffer *ab, void **ptr, int samples)
{
- int offset = ab->buffer->samples;
- ab->buffer->samples += mpa->samples;
- mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
- mp_audio_copy(ab->buffer, offset, mpa, 0, mpa->samples);
+ mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
+ copy_planes(ab, ab->data, ab->num_samples, (uint8_t **)ptr, 0, samples);
+ ab->num_samples += samples;
}
// Prepend silence to the start of the buffer.
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples)
{
assert(samples >= 0);
- int oldlen = ab->buffer->samples;
- ab->buffer->samples += samples;
- mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
- mp_audio_copy(ab->buffer, samples, ab->buffer, 0, oldlen);
- mp_audio_fill_silence(ab->buffer, 0, samples);
+ mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
+ copy_planes(ab, ab->data, samples, ab->data, 0, ab->num_samples);
+ ab->num_samples += samples;
+ for (int n = 0; n < ab->num_planes; n++)
+ af_fill_silence(ab->data[n], samples * ab->sstride, ab->format);
}
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples)
{
- assert(samples >= 0 && samples <= ab->buffer->samples);
- int oldlen = ab->buffer->samples;
- ab->buffer->samples += samples;
- mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
- mp_audio_copy(ab->buffer, oldlen, ab->buffer, oldlen - samples, samples);
+ assert(samples >= 0 && samples <= ab->num_samples);
+ mp_audio_buffer_preallocate_min(ab, ab->num_samples + samples);
+ copy_planes(ab, ab->data, ab->num_samples,
+ ab->data, ab->num_samples - samples, samples);
+ ab->num_samples += samples;
}
// Get the start of the current readable buffer.
-void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa)
+void mp_audio_buffer_peek(struct mp_audio_buffer *ab, uint8_t ***ptr,
+ int *samples)
{
- *out_mpa = *ab->buffer;
+ *ptr = ab->data;
+ *samples = ab->num_samples;
}
// Skip leading samples. (Used with mp_audio_buffer_peek() to read data.)
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples)
{
- assert(samples >= 0 && samples <= ab->buffer->samples);
- mp_audio_copy(ab->buffer, 0, ab->buffer, samples,
- ab->buffer->samples - samples);
- ab->buffer->samples -= samples;
+ assert(samples >= 0 && samples <= ab->num_samples);
+ copy_planes(ab, ab->data, 0, ab->data, samples, ab->num_samples - samples);
+ ab->num_samples -= samples;
}
void mp_audio_buffer_clear(struct mp_audio_buffer *ab)
{
- ab->buffer->samples = 0;
+ ab->num_samples = 0;
}
// Return number of buffered audio samples
int mp_audio_buffer_samples(struct mp_audio_buffer *ab)
{
- return ab->buffer->samples;
+ return ab->num_samples;
}
// Return amount of buffered audio in seconds.
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab)
{
- return ab->buffer->samples / (double)ab->buffer->rate;
+ return ab->num_samples / (double)ab->srate;
}
diff --git a/audio/audio_buffer.h b/audio/audio_buffer.h
index 212d187..0d7b66a 100644
--- a/audio/audio_buffer.h
+++ b/audio/audio_buffer.h
@@ -19,24 +19,18 @@
#define MP_AUDIO_BUFFER_H
struct mp_audio_buffer;
-struct mp_audio;
struct mp_chmap;
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx);
-void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt);
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
const struct mp_chmap *channels, int srate);
-void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
- struct mp_audio *out_fmt);
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples);
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab);
-void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int minsamples,
- struct mp_audio *out_buffer);
-void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples);
-void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa);
+void mp_audio_buffer_append(struct mp_audio_buffer *ab, void **ptr, int samples);
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples);
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples);
-void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa);
+void mp_audio_buffer_peek(struct mp_audio_buffer *ab, uint8_t ***ptr,
+ int *samples);
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
int mp_audio_buffer_samples(struct mp_audio_buffer *ab);
diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c
index 401e26f..5eca89b 100644
--- a/audio/decode/dec_audio.c
+++ b/audio/decode/dec_audio.c
@@ -29,6 +29,7 @@
#include "common/msg.h"
#include "common/recorder.h"
#include "misc/bstr.h"
+#include "options/options.h"
#include "stream/stream.h"
#include "demux/demux.h"
@@ -39,8 +40,6 @@
#include "ad.h"
#include "audio/format.h"
-#include "audio/filter/af.h"
-
extern const struct ad_functions ad_lavc;
// Not a real codec - specially treated.
@@ -197,6 +196,12 @@ static void fix_audio_pts(struct dec_audio *da)
da->pts += mp_aframe_duration(da->current_frame);
}
+static bool is_new_segment(struct dec_audio *da, struct demux_packet *p)
+{
+ return p->segmented &&
+ (p->start != da->start || p->end != da->end || p->codec != da->codec);
+}
+
void audio_work(struct dec_audio *da)
{
if (da->current_frame || !da->ad_driver)
@@ -209,7 +214,7 @@ void audio_work(struct dec_audio *da)
return;
}
- if (da->packet && da->packet->new_segment) {
+ if (da->packet && is_new_segment(da, da->packet)) {
assert(!da->new_segment);
da->new_segment = da->packet;
da->packet = NULL;
@@ -261,8 +266,6 @@ void audio_work(struct dec_audio *da)
da->start = new_segment->start;
da->end = new_segment->end;
- new_segment->new_segment = false;
-
da->packet = new_segment;
da->current_state = DATA_AGAIN;
}
diff --git a/audio/filter/af.c b/audio/filter/af.c
index 0df0f28..5838c2e 100644
--- a/audio/filter/af.c
+++ b/audio/filter/af.c
@@ -31,25 +31,16 @@
#include "af.h"
// Static list of filters
-extern const struct af_info af_info_channels;
extern const struct af_info af_info_format;
-extern const struct af_info af_info_volume;
-extern const struct af_info af_info_equalizer;
-extern const struct af_info af_info_pan;
extern const struct af_info af_info_lavcac3enc;
extern const struct af_info af_info_lavrresample;
extern const struct af_info af_info_scaletempo;
-extern const struct af_info af_info_bs2b;
extern const struct af_info af_info_lavfi;
extern const struct af_info af_info_lavfi_bridge;
extern const struct af_info af_info_rubberband;
static const struct af_info *const filter_list[] = {
- &af_info_channels,
&af_info_format,
- &af_info_volume,
- &af_info_equalizer,
- &af_info_pan,
&af_info_lavcac3enc,
&af_info_lavrresample,
#if HAVE_RUBBERBAND
@@ -175,7 +166,7 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
.data = talloc_zero(af, struct mp_audio),
.log = mp_log_new(af, s->log, name),
.opts = s->opts,
- .replaygain_data = s->replaygain_data,
+ .global = s->global,
.out_pool = mp_audio_pool_create(af),
};
struct m_config *config =
@@ -546,6 +537,7 @@ struct af_stream *af_new(struct mpv_global *global)
s->first->next = s->last;
s->last->prev = s->first;
s->opts = global->opts;
+ s->global = global;
return s;
}
diff --git a/audio/filter/af.h b/audio/filter/af.h
index 553fc03..f27edee 100644
--- a/audio/filter/af.h
+++ b/audio/filter/af.h
@@ -22,6 +22,11 @@
#include <stdbool.h>
#include <sys/types.h>
+#include "config.h"
+#if !(HAVE_LIBAF && HAVE_GPL)
+#error "libaf/GPL disabled"
+#endif
+
#include "options/options.h"
#include "audio/format.h"
#include "audio/chmap.h"
@@ -55,7 +60,7 @@ struct af_instance {
char *full_name;
struct mp_log *log;
struct MPOpts *opts;
- struct replaygain_data *replaygain_data;
+ struct mpv_global *global;
int (*control)(struct af_instance *af, int cmd, void *arg);
void (*uninit)(struct af_instance *af);
/* Feed a frame. The frame is NULL if EOF was reached, and the filter
@@ -98,7 +103,7 @@ struct af_stream {
struct mp_log *log;
struct MPOpts *opts;
- struct replaygain_data *replaygain_data;
+ struct mpv_global *global;
};
// Return values
@@ -113,11 +118,6 @@ struct af_stream {
enum af_control {
AF_CONTROL_REINIT = 1,
AF_CONTROL_RESET,
- AF_CONTROL_SET_VOLUME,
- AF_CONTROL_SET_PAN_LEVEL,
- AF_CONTROL_SET_PAN_NOUT,
- AF_CONTROL_SET_PAN_BALANCE,
- AF_CONTROL_GET_PAN_BALANCE,
AF_CONTROL_SET_PLAYBACK_SPEED,
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
AF_CONTROL_GET_METADATA,
diff --git a/audio/filter/af_channels.c b/audio/filter/af_channels.c
deleted file mode 100644
index 7cd7810..0000000
--- a/audio/filter/af_channels.c
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Audio filter that adds and removes channels, according to the
- * command line parameter channels. It is stupid and can only add
- * silence or copy channels, not mix or filter.
- *
- * Original author: Anders
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-
-#include "common/common.h"
-#include "af.h"
-
-#define FR 0
-#define TO 1
-
-typedef struct af_channels_s{
- int route[AF_NCH][2];
- int nch, nr;
- int router;
- char *routes;
-}af_channels_t;
-
-// Local function for copying data
-static void copy(struct af_instance *af, void* in, void* out,
- int ins, int inos,int outs, int outos, int len, int bps)
-{
- switch(bps){
- case 1:{
- int8_t* tin = (int8_t*)in;
- int8_t* tout = (int8_t*)out;
- tin += inos;
- tout += outos;
- len = len/ins;
- while(len--){
- *tout=*tin;
- tin +=ins;
- tout+=outs;
- }
- break;
- }
- case 2:{
- int16_t* tin = (int16_t*)in;
- int16_t* tout = (int16_t*)out;
- tin += inos;
- tout += outos;
- len = len/(2*ins);
- while(len--){
- *tout=*tin;
- tin +=ins;
- tout+=outs;
- }
- break;
- }
- case 3:{
- int8_t* tin = (int8_t*)in;
- int8_t* tout = (int8_t*)out;
- tin += 3 * inos;
- tout += 3 * outos;
- len = len / ( 3 * ins);
- while (len--) {
- tout[0] = tin[0];
- tout[1] = tin[1];
- tout[2] = tin[2];
- tin += 3 * ins;
- tout += 3 * outs;
- }
- break;
- }
- case 4:{
- int32_t* tin = (int32_t*)in;
- int32_t* tout = (int32_t*)out;
- tin += inos;
- tout += outos;
- len = len/(4*ins);
- while(len--){
- *tout=*tin;
- tin +=ins;
- tout+=outs;
- }
- break;
- }
- case 8:{
- int64_t* tin = (int64_t*)in;
- int64_t* tout = (int64_t*)out;
- tin += inos;
- tout += outos;
- len = len/(8*ins);
- while(len--){
- *tout=*tin;
- tin +=ins;
- tout+=outs;
- }
- break;
- }
- default:
- MP_ERR(af, "Unsupported number of bytes/sample: %i"
- " please report this error on the MPlayer mailing list. \n",bps);
- }
-}
-
-// Make sure the routes are sane
-static int check_routes(struct af_instance *af, int nin, int nout)
-{
- af_channels_t* s = af->priv;
- int i;
- if((s->nr < 1) || (s->nr > AF_NCH)){
- MP_ERR(af, "The number of routing pairs must be"
- " between 1 and %i. Current value is %i\n",AF_NCH,s->nr);
- return AF_ERROR;
- }
-
- for(i=0;i<s->nr;i++){
- if((s->route[i][FR] >= nin) || (s->route[i][TO] >= nout)){
- MP_ERR(af, "Invalid routing in pair nr. %i.\n", i);
- return AF_ERROR;
- }
- }
- return AF_OK;
-}
-
-// Initialization and runtime control
-static int control(struct af_instance* af, int cmd, void* arg)
-{
- af_channels_t* s = af->priv;
- switch(cmd){
- case AF_CONTROL_REINIT: ;
-
- struct mp_chmap chmap;
- mp_chmap_set_unknown(&chmap, s->nch);
- mp_audio_set_channels(af->data, &chmap);
-
- // Set default channel assignment
- if(!s->router){
- int i;
- // Make sure this filter isn't redundant
- if(af->data->nch == ((struct mp_audio*)arg)->nch)
- return AF_DETACH;
-
- // If mono: fake stereo
- if(((struct mp_audio*)arg)->nch == 1){
- s->nr = MPMIN(af->data->nch,2);
- for(i=0;i<s->nr;i++){
- s->route[i][FR] = 0;
- s->route[i][TO] = i;
- }
- }
- else{
- s->nr = MPMIN(af->data->nch, ((struct mp_audio*)arg)->nch);
- for(i=0;i<s->nr;i++){
- s->route[i][FR] = i;
- s->route[i][TO] = i;
- }
- }
- }
-
- af->data->rate = ((struct mp_audio*)arg)->rate;
- mp_audio_force_interleaved_format((struct mp_audio*)arg);
- mp_audio_set_format(af->data, ((struct mp_audio*)arg)->format);
- return check_routes(af,((struct mp_audio*)arg)->nch,af->data->nch);
- }
- return AF_UNKNOWN;
-}
-
-static int filter_frame(struct af_instance *af, struct mp_audio *c)
-{
- af_channels_t* s = af->priv;
- int i;
-
- if (!c)
- return 0;
-
- struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
- if (!l) {
- talloc_free(c);
- return -1;
- }
- mp_audio_copy_attributes(l, c);
-
- // Reset unused channels
- memset(l->planes[0],0,mp_audio_psize(c) / c->nch * l->nch);
-
- if(AF_OK == check_routes(af,c->nch,l->nch))
- for(i=0;i<s->nr;i++)
- copy(af, c->planes[0],l->planes[0],c->nch,s->route[i][FR],
- l->nch,s->route[i][TO],mp_audio_psize(c),c->bps);
-
- talloc_free(c);
- af_add_output_frame(af, l);
- return 0;
-}
-
-// Allocate memory and set function pointers
-static int af_open(struct af_instance* af){
- af->control=control;
- af->filter_frame = filter_frame;
- af_channels_t *s = af->priv;
-
- MP_WARN(af, "This filter is deprecated (no replacement).\n");
-
- // If router scan commandline for routing pairs
- if(s->routes && s->routes[0]){
- char* cp = s->routes;
- int ch = 0;
- // Scan for pairs on commandline
- do {
- int n = 0;
- if (ch >= AF_NCH) {
- MP_FATAL(af, "Can't have more than %d routes.\n", AF_NCH);
- return AF_ERROR;
- }
- sscanf(cp, "%i-%i%n" ,&s->route[ch][FR], &s->route[ch][TO], &n);
- MP_VERBOSE(af, "Routing from channel %i to"
- " channel %i\n",s->route[ch][FR],s->route[ch][TO]);
- cp = &cp[n];
- ch++;
- } while(*cp == ',' && *(cp++));
- s->nr = ch;
- if (s->nr > 0)
- s->router = 1;
- }
-
- return AF_OK;
-}
-
-#define OPT_BASE_STRUCT af_channels_t
-const struct af_info af_info_channels = {
- .info = "Insert or remove channels",
- .name = "channels",
- .open = af_open,
- .priv_size = sizeof(af_channels_t),
- .options = (const struct m_option[]) {
- OPT_INTRANGE("nch", nch, 0, 1, AF_NCH, OPTDEF_INT(2)),
- OPT_STRING("routes", routes, 0),
- {0}
- },
-};
diff --git a/audio/filter/af_equalizer.c b/audio/filter/af_equalizer.c
deleted file mode 100644
index 3f132fd..0000000
--- a/audio/filter/af_equalizer.c
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Equalizer filter, implementation of a 10 band time domain graphic
- * equalizer using IIR filters. The IIR filters are implemented using a
- * Direct Form II approach, but has been modified (b1 == 0 always) to
- * save computation.
- *
- * Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <inttypes.h>
-#include <math.h>
-
-#include "common/common.h"
-#include "af.h"
-
-#define L 2 // Storage for filter taps
-#define KM 10 // Max number of bands
-
-#define Q 1.2247449 /* Q value for band-pass filters 1.2247=(3/2)^(1/2)
- gives 4dB suppression @ Fc*2 and Fc/2 */
-
-/* Center frequencies for band-pass filters
- The different frequency bands are:
- nr. center frequency
- 0 31.25 Hz
- 1 62.50 Hz
- 2 125.0 Hz
- 3 250.0 Hz
- 4 500.0 Hz
- 5 1.000 kHz
- 6 2.000 kHz
- 7 4.000 kHz
- 8 8.000 kHz
- 9 16.00 kHz
-*/
-#define CF {31.25,62.5,125,250,500,1000,2000,4000,8000,16000}
-
-// Maximum and minimum gain for the bands
-#define G_MAX +12.0
-#define G_MIN -12.0
-
-// Data for specific instances of this filter
-typedef struct af_equalizer_s
-{
- float a[KM][L]; // A weights
- float b[KM][L]; // B weights
- float wq[AF_NCH][KM][L]; // Circular buffer for W data
- float g[AF_NCH][KM]; // Gain factor for each channel and band
- int K; // Number of used eq bands
- int channels; // Number of channels
- float gain_factor; // applied at output to avoid clipping
- double p[KM];
-} af_equalizer_t;
-
-// 2nd order Band-pass Filter design
-static void bp2(float* a, float* b, float fc, float q){
- double th= 2.0 * M_PI * fc;
- double C = (1.0 - tan(th*q/2.0))/(1.0 + tan(th*q/2.0));
-
- a[0] = (1.0 + C) * cos(th);
- a[1] = -1 * C;
-
- b[0] = (1.0 - C)/2.0;
- b[1] = -1.0050;
-}
-
-// Initialization and runtime control
-static int control(struct af_instance* af, int cmd, void* arg)
-{
- af_equalizer_t* s = (af_equalizer_t*)af->priv;
-
- switch(cmd){
- case AF_CONTROL_REINIT:{
- int k =0, i =0;
- float F[KM] = CF;
-
- s->gain_factor=0.0;
-
- // Sanity check
- if(!arg) return AF_ERROR;
-
- mp_audio_copy_config(af->data, (struct mp_audio*)arg);
- mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
-
- // Calculate number of active filters
- s->K=KM;
- while(F[s->K-1] > (float)af->data->rate/2.2)
- s->K--;
-
- if(s->K != KM)
- MP_INFO(af, "Limiting the number of filters to"
- " %i due to low sample rate.\n",s->K);
-
- // Generate filter taps
- for(k=0;k<s->K;k++)
- bp2(s->a[k],s->b[k],F[k]/((float)af->data->rate),Q);
-
- // Calculate how much this plugin adds to the overall time delay
- af->delay = 2.0 / (double)af->data->rate;
-
- // Calculate gain factor to prevent clipping at output
- for(k=0;k<AF_NCH;k++)
- {
- for(i=0;i<KM;i++)
- {
- if(s->gain_factor < s->g[k][i]) s->gain_factor=s->g[k][i];
- }
- }
-
- s->gain_factor=log10(s->gain_factor + 1.0) * 20.0;
-
- if(s->gain_factor > 0.0)
- {
- s->gain_factor=0.1+(s->gain_factor/12.0);
- }else{
- s->gain_factor=1;
- }
-
- return af_test_output(af,arg);
- }
- }
- return AF_UNKNOWN;
-}
-
-static int filter(struct af_instance* af, struct mp_audio* data)
-{
- struct mp_audio* c = data; // Current working data
- if (!c)
- return 0;
- af_equalizer_t* s = (af_equalizer_t*)af->priv; // Setup
- uint32_t ci = af->data->nch; // Index for channels
- uint32_t nch = af->data->nch; // Number of channels
-
- if (af_make_writeable(af, data) < 0) {
- talloc_free(data);
- return -1;
- }
-
- while(ci--){
- float* g = s->g[ci]; // Gain factor
- float* in = ((float*)c->planes[0])+ci;
- float* out = ((float*)c->planes[0])+ci;
- float* end = in + c->samples*c->nch; // Block loop end
-
- while(in < end){
- register int k = 0; // Frequency band index
- register float yt = *in; // Current input sample
- in+=nch;
-
- // Run the filters
- for(;k<s->K;k++){
- // Pointer to circular buffer wq
- register float* wq = s->wq[ci][k];
- // Calculate output from AR part of current filter
- register float w=yt*s->b[k][0] + wq[0]*s->a[k][0] + wq[1]*s->a[k][1];
- // Calculate output form MA part of current filter
- yt+=(w + wq[1]*s->b[k][1])*g[k];
- // Update circular buffer
- wq[1] = wq[0];
- wq[0] = w;
- }
- // Calculate output
- *out=yt*s->gain_factor;
- out+=nch;
- }
- }
- af_add_output_frame(af, data);
- return 0;
-}
-
-// Allocate memory and set function pointers
-static int af_open(struct af_instance* af){
- MP_WARN(af, "This filter is deprecated. Use 'anequalizer' or 'firequalizer' instead.\n");
- af->control=control;
- af->filter_frame = filter;
- af_equalizer_t *priv = af->priv;
- for(int i=0;i<AF_NCH;i++){
- for(int j=0;j<KM;j++){
- priv->g[i][j] = pow(10.0,MPCLAMP(priv->p[j],G_MIN,G_MAX)/20.0)-1.0;
- }
- }
- return AF_OK;
-}
-
-#define OPT_BASE_STRUCT af_equalizer_t
-const struct af_info af_info_equalizer = {
- .info = "Equalizer audio filter",
- .name = "equalizer",
- .open = af_open,
- .priv_size = sizeof(af_equalizer_t),
- .options = (const struct m_option[]) {
-#define BAND(n) OPT_DOUBLE("e" #n, p[n], 0)
- BAND(0), BAND(1), BAND(2), BAND(3), BAND(4),
- BAND(5), BAND(6), BAND(7), BAND(8), BAND(9),
- {0}
- },
-};
diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c
index 33e685e..14aa53b 100644
--- a/audio/filter/af_lavcac3enc.c
+++ b/audio/filter/af_lavcac3enc.c
@@ -400,7 +400,7 @@ static int af_open(struct af_instance* af){
}
}
if (i >= 19) {
- MP_WARN(af, "unable set unsupported bitrate %d, use default "
+ MP_WARN(af, "unable set unsupported bitrate %d, using default "
"bitrate (check manpage to see supported bitrates).\n",
s->cfg_bit_rate);
}
diff --git a/audio/filter/af_lavfi.c b/audio/filter/af_lavfi.c
index 47edf20..ab8a026 100644
--- a/audio/filter/af_lavfi.c
+++ b/audio/filter/af_lavfi.c
@@ -92,6 +92,7 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
void *tmp = talloc_new(NULL);
struct priv *p = af->priv;
AVFilterContext *in = NULL, *out = NULL;
+ bool ok = false;
if (!p->is_bridge && bstr0(p->cfg_graph).len == 0) {
MP_FATAL(af, "lavfi: no filter graph set\n");
@@ -177,14 +178,17 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
assert(out->nb_inputs == 1);
assert(in->nb_outputs == 1);
- talloc_free(tmp);
- return true;
-
+ ok = true;
error:
- MP_FATAL(af, "Can't configure libavfilter graph.\n");
- avfilter_graph_free(&graph);
+
+ if (!ok) {
+ MP_FATAL(af, "Can't configure libavfilter graph.\n");
+ avfilter_graph_free(&graph);
+ }
+ avfilter_inout_free(&inputs);
+ avfilter_inout_free(&outputs);
talloc_free(tmp);
- return false;
+ return ok;
}
static void reset(struct af_instance *af)
@@ -261,7 +265,7 @@ static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
if (!p->metadata)
p->metadata = talloc_zero(p, struct mp_tags);
- mp_tags_copy_from_av_dictionary(p->metadata, av_frame_get_metadata(frame));
+ mp_tags_copy_from_av_dictionary(p->metadata, frame->metadata);
#endif
}
diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c
index c18f8cc..55eb6b0 100644
--- a/audio/filter/af_lavrresample.c
+++ b/audio/filter/af_lavrresample.c
@@ -27,326 +27,24 @@
#include <math.h>
#include <assert.h>
-#include <libavutil/opt.h>
-#include <libavutil/common.h>
-#include <libavutil/samplefmt.h>
-#include <libavutil/channel_layout.h>
-#include <libavutil/mathematics.h>
-
#include "common/common.h"
#include "config.h"
-#define HAVE_LIBSWRESAMPLE HAVE_IS_FFMPEG
-#define HAVE_LIBAVRESAMPLE HAVE_IS_LIBAV
-
-#if HAVE_LIBAVRESAMPLE
-#include <libavresample/avresample.h>
-#elif HAVE_LIBSWRESAMPLE
-#include <libswresample/swresample.h>
-#define AVAudioResampleContext SwrContext
-#define avresample_alloc_context swr_alloc
-#define avresample_open swr_init
-#define avresample_close(x) do { } while(0)
-#define avresample_free swr_free
-#define avresample_available(x) 0
-#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
- swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
-#define avresample_set_channel_mapping swr_set_channel_mapping
-#define avresample_set_compensation swr_set_compensation
-#else
-#error "config.h broken or no resampler found"
-#endif
-
#include "common/av_common.h"
#include "common/msg.h"
#include "options/m_option.h"
#include "audio/filter/af.h"
#include "audio/fmt-conversion.h"
#include "osdep/endian.h"
-
-struct af_resample_opts {
- int filter_size;
- int phase_shift;
- int linear;
- double cutoff;
- int normalize;
-};
+#include "audio/aconverter.h"
struct af_resample {
int allow_detach;
- char **avopts;
double playback_speed;
- bool is_resampling;
- struct AVAudioResampleContext *avrctx;
- struct mp_audio avrctx_fmt; // output format of avrctx
- struct mp_audio pool_fmt; // format used to allocate frames for avrctx output
- struct mp_audio pre_out_fmt; // format before final conversion (S24)
- struct AVAudioResampleContext *avrctx_out; // for output channel reordering
- struct af_resample_opts opts; // opts requested by the user
- // At least libswresample keeps a pointer around for this:
- int reorder_in[MP_NUM_CHANNELS];
- int reorder_out[MP_NUM_CHANNELS];
- struct mp_audio_pool *reorder_buffer;
-
- int in_rate_af; // filter input sample rate
- int in_rate; // actual rate (used by lavr), adjusted for playback speed
- int in_format;
- struct mp_chmap in_channels;
- int out_rate;
- int out_format;
- struct mp_chmap out_channels;
-};
-
-#if HAVE_LIBAVRESAMPLE
-static double get_delay(struct af_resample *s)
-{
- return avresample_get_delay(s->avrctx) / (double)s->in_rate +
- avresample_available(s->avrctx) / (double)s->out_rate;
-}
-static int get_out_samples(struct af_resample *s, int in_samples)
-{
- return avresample_get_out_samples(s->avrctx, in_samples);
-}
-#else
-static double get_delay(struct af_resample *s)
-{
- int64_t base = s->in_rate * (int64_t)s->out_rate;
- return swr_get_delay(s->avrctx, base) / (double)base;
-}
-static int get_out_samples(struct af_resample *s, int in_samples)
-{
- return swr_get_out_samples(s->avrctx, in_samples);
-}
-#endif
-
-static void close_lavrr(struct af_instance *af)
-{
- struct af_resample *s = af->priv;
-
- if (s->avrctx)
- avresample_close(s->avrctx);
- avresample_free(&s->avrctx);
- if (s->avrctx_out)
- avresample_close(s->avrctx_out);
- avresample_free(&s->avrctx_out);
-}
-
-static int resample_frame(struct AVAudioResampleContext *r,
- struct mp_audio *out, struct mp_audio *in)
-{
- return avresample_convert(r,
- out ? (uint8_t **)out->planes : NULL,
- out ? mp_audio_get_allocated_size(out) : 0,
- out ? out->samples : 0,
- in ? (uint8_t **)in->planes : NULL,
- in ? mp_audio_get_allocated_size(in) : 0,
- in ? in->samples : 0);
-}
-
-static double af_resample_default_cutoff(int filter_size)
-{
- return FFMAX(1.0 - 6.5 / (filter_size + 8), 0.80);
-}
-
-static int rate_from_speed(int rate, double speed)
-{
- return lrint(rate * speed);
-}
-
-static struct mp_chmap fudge_pairs[][2] = {
- {MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
- {MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
- {MP_CHMAP2(SDL, SDR), MP_CHMAP2(SL, SR)},
- {MP_CHMAP2(SL, SR), MP_CHMAP2(SDL, SDR)},
+ struct mp_resample_opts opts;
+ struct mp_aconverter *converter;
};
-// Modify out_layout and return the new value. The intention is reducing the
-// loss libswresample's rematrixing will cause by exchanging similar, but
-// strictly speaking incompatible channel pairs. For example, 7.1 should be
-// changed to 7.1(wide) without dropping the SL/SR channels. (We still leave
-// it to libswresample to create the remix matrix.)
-static uint64_t fudge_layout_conversion(struct af_instance *af,
- uint64_t in, uint64_t out)
-{
- for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
- uint64_t a = mp_chmap_to_lavc(&fudge_pairs[n][0]);
- uint64_t b = mp_chmap_to_lavc(&fudge_pairs[n][1]);
- if ((in & a) == a && (in & b) == 0 &&
- (out & a) == 0 && (out & b) == b)
- {
- out = (out & ~b) | a;
-
- MP_VERBOSE(af, "Fudge: %s -> %s\n",
- mp_chmap_to_str(&fudge_pairs[n][0]),
- mp_chmap_to_str(&fudge_pairs[n][1]));
- }
- }
- return out;
-}
-
-// mp_chmap_get_reorder() performs:
-// to->speaker[n] = from->speaker[src[n]]
-// but libavresample does:
-// to->speaker[dst[n]] = from->speaker[n]
-static void transpose_order(int *map, int num)
-{
- int nmap[MP_NUM_CHANNELS] = {0};
- for (int n = 0; n < num; n++) {
- for (int i = 0; i < num; i++) {
- if (map[n] == i)
- nmap[i] = n;
- }
- }
- memcpy(map, nmap, sizeof(nmap));
-}
-
-static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
- struct mp_audio *out, bool verbose)
-{
- struct af_resample *s = af->priv;
-
- close_lavrr(af);
-
- s->avrctx = avresample_alloc_context();
- s->avrctx_out = avresample_alloc_context();
- if (!s->avrctx || !s->avrctx_out)
- goto error;
-
- enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
- enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
- enum AVSampleFormat out_samplefmtp = av_get_planar_sample_fmt(out_samplefmt);
-
- if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
- out_samplefmt == AV_SAMPLE_FMT_NONE ||
- out_samplefmtp == AV_SAMPLE_FMT_NONE)
- goto error;
-
- s->out_rate = out->rate;
- s->in_rate_af = in->rate;
- s->in_rate = rate_from_speed(in->rate, s->playback_speed);
- s->out_format = out->format;
- s->in_format = in->format;
- s->out_channels= out->channels;
- s->in_channels = in->channels;
-
- av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0);
- av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0);
- av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0);
-
- av_opt_set_double(s->avrctx, "cutoff", s->opts.cutoff, 0);
-
- int normalize = s->opts.normalize;
- if (normalize < 0)
- normalize = af->opts->audio_normalize;
-#if HAVE_LIBSWRESAMPLE
- av_opt_set_double(s->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
-#else
- av_opt_set_int(s->avrctx, "normalize_mix_level", !!normalize, 0);
-#endif
-
- if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
- goto error;
-
- struct mp_chmap map_in = in->channels;
- struct mp_chmap map_out = out->channels;
-
- // Try not to do any remixing if at least one is "unknown".
- if (mp_chmap_is_unknown(&map_in) || mp_chmap_is_unknown(&map_out)) {
- mp_chmap_set_unknown(&map_in, map_in.num);
- mp_chmap_set_unknown(&map_out, map_out.num);
- }
-
- // unchecked: don't take any channel reordering into account
- uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
- uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
-
- struct mp_chmap in_lavc, out_lavc;
- mp_chmap_from_lavc(&in_lavc, in_ch_layout);
- mp_chmap_from_lavc(&out_lavc, out_ch_layout);
-
- if (verbose && !mp_chmap_equals(&in_lavc, &out_lavc)) {
- MP_VERBOSE(af, "Remix: %s -> %s\n", mp_chmap_to_str(&in_lavc),
- mp_chmap_to_str(&out_lavc));
- }
-
- if (in_lavc.num != map_in.num) {
- // For handling NA channels, we would have to add a planarization step.
- MP_FATAL(af, "Unsupported channel remapping.\n");
- goto error;
- }
-
- mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
- transpose_order(s->reorder_in, map_in.num);
-
- if (mp_chmap_equals(&out_lavc, &map_out)) {
- // No intermediate step required - output new format directly.
- out_samplefmtp = out_samplefmt;
- } else {
- // Verify that we really just reorder and/or insert NA channels.
- struct mp_chmap withna = out_lavc;
- mp_chmap_fill_na(&withna, map_out.num);
- if (withna.num != map_out.num)
- goto error;
- }
- mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
-
- s->avrctx_fmt = *out;
- mp_audio_set_channels(&s->avrctx_fmt, &out_lavc);
- mp_audio_set_format(&s->avrctx_fmt, af_from_avformat(out_samplefmtp));
-
- s->pre_out_fmt = *out;
-
- // If there are NA channels, the final output will have more channels than
- // the avrctx output. Also, avrctx will output planar (out_samplefmtp was
- // not overwritten). Allocate the output frame with more channels, so the
- // NA channels can be trivially added.
- s->pool_fmt = s->avrctx_fmt;
- if (map_out.num > out_lavc.num)
- mp_audio_set_channels(&s->pool_fmt, &map_out);
-
- out_ch_layout = fudge_layout_conversion(af, in_ch_layout, out_ch_layout);
-
- // Real conversion; output is input to avrctx_out.
- av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
- av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
- av_opt_set_int(s->avrctx, "in_sample_rate", s->in_rate, 0);
- av_opt_set_int(s->avrctx, "out_sample_rate", s->out_rate, 0);
- av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
- av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
-
- // Just needs the correct number of channels for deplanarization.
- struct mp_chmap fake_chmap;
- mp_chmap_set_unknown(&fake_chmap, map_out.num);
- uint64_t fake_out_ch_layout = mp_chmap_to_lavc_unchecked(&fake_chmap);
- if (!fake_out_ch_layout)
- goto error;
- av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
- av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
-
- av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
- av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
- av_opt_set_int(s->avrctx_out, "in_sample_rate", s->out_rate, 0);
- av_opt_set_int(s->avrctx_out, "out_sample_rate", s->out_rate, 0);
-
- // API has weird requirements, quoting avresample.h:
- // * This function can only be called when the allocated context is not open.
- // * Also, the input channel layout must have already been set.
- avresample_set_channel_mapping(s->avrctx, s->reorder_in);
-
- s->is_resampling = false;
-
- if (avresample_open(s->avrctx) < 0 || avresample_open(s->avrctx_out) < 0) {
- MP_ERR(af, "Cannot open Libavresample Context. \n");
- goto error;
- }
- return AF_OK;
-
-error:
- close_lavrr(af);
- return AF_ERROR;
-}
-
-
static int control(struct af_instance *af, int cmd, void *arg)
{
struct af_resample *s = af->priv;
@@ -378,8 +76,12 @@ static int control(struct af_instance *af, int cmd, void *arg)
mp_chmap_equals(&in->channels, &orig_in.channels))
? AF_OK : AF_FALSE;
- if (r == AF_OK)
- r = configure_lavrr(af, in, out, true);
+ if (r == AF_OK) {
+ if (!mp_aconverter_reconfig(s->converter,
+ in->rate, in->format, in->channels,
+ out->rate, out->format, out->channels))
+ r = AF_ERROR;
+ }
return r;
}
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
@@ -387,17 +89,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
return AF_OK;
}
case AF_CONTROL_RESET:
- if (s->avrctx) {
-#if HAVE_LIBSWRESAMPLE
- swr_close(s->avrctx);
- if (swr_init(s->avrctx) < 0) {
- close_lavrr(af);
- return AF_ERROR;
- }
-#else
- while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
-#endif
- }
+ mp_aconverter_flush(s->converter);
return AF_OK;
}
return AF_UNKNOWN;
@@ -405,149 +97,40 @@ static int control(struct af_instance *af, int cmd, void *arg)
static void uninit(struct af_instance *af)
{
- close_lavrr(af);
-}
-
-// The LSB is always ignored.
-#if BYTE_ORDER == BIG_ENDIAN
-#define SHIFT24(x) ((3-(x))*8)
-#else
-#define SHIFT24(x) (((x)+1)*8)
-#endif
-
-static void extra_output_conversion(struct af_instance *af, struct mp_audio *mpa)
-{
- for (int p = 0; p < mpa->num_planes; p++) {
- void *ptr = mpa->planes[p];
- int total = mpa->samples * mpa->spf;
- if (af_fmt_from_planar(mpa->format) == AF_FORMAT_FLOAT) {
- for (int s = 0; s < total; s++)
- ((float *)ptr)[s] = av_clipf(((float *)ptr)[s], -1.0f, 1.0f);
- } else if (af_fmt_from_planar(mpa->format) == AF_FORMAT_DOUBLE) {
- for (int s = 0; s < total; s++)
- ((double *)ptr)[s] = MPCLAMP(((double *)ptr)[s], -1.0, 1.0);
- }
- }
-}
-
-// This relies on the tricky way mpa was allocated.
-static void reorder_planes(struct mp_audio *mpa, int *reorder,
- struct mp_chmap *newmap)
-{
- struct mp_audio prev = *mpa;
- mp_audio_set_channels(mpa, newmap);
-
- // The trailing planes were never written by avrctx, they're the NA channels.
- int next_na = prev.num_planes;
+ struct af_resample *s = af->priv;
- for (int n = 0; n < mpa->num_planes; n++) {
- int src = reorder[n];
- assert(src >= -1 && src < prev.num_planes);
- if (src >= 0) {
- mpa->planes[n] = prev.planes[src];
- } else {
- assert(next_na < mpa->num_planes);
- mpa->planes[n] = prev.planes[next_na++];
- af_fill_silence(mpa->planes[n], mpa->sstride * mpa->samples,
- mpa->format);
- }
- }
+ talloc_free(s->converter);
}
-static int filter_resample(struct af_instance *af, struct mp_audio *in)
+static int filter(struct af_instance *af, struct mp_audio *in)
{
struct af_resample *s = af->priv;
- struct mp_audio *out = NULL;
-
- if (!s->avrctx)
- goto error;
- int samples = get_out_samples(s, in ? in->samples : 0);
-
- struct mp_audio out_format = s->pool_fmt;
- out = mp_audio_pool_get(af->out_pool, &out_format, samples);
- if (!out)
- goto error;
- if (in)
- mp_audio_copy_attributes(out, in);
-
- if (out->samples) {
- out->samples = resample_frame(s->avrctx, out, in);
- if (out->samples < 0)
- goto error;
- }
-
- struct mp_audio real_out = *out;
- mp_audio_copy_config(out, &s->avrctx_fmt);
-
- if (out->samples && !mp_audio_config_equals(out, &s->pre_out_fmt)) {
- assert(af_fmt_is_planar(out->format) && out->format == real_out.format);
- reorder_planes(out, s->reorder_out, &s->pool_fmt.channels);
- if (!mp_audio_config_equals(out, &s->pre_out_fmt)) {
- struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer,
- &s->pre_out_fmt,
- out->samples);
- if (!new)
- goto error;
- mp_audio_copy_attributes(new, out);
- int out_samples = resample_frame(s->avrctx_out, new, out);
- talloc_free(out);
- out = new;
- if (out_samples != new->samples)
- goto error;
- }
- }
+ mp_aconverter_set_speed(s->converter, s->playback_speed);
- extra_output_conversion(af, out);
+ af->filter_out(af);
+ struct mp_aframe *aframe = mp_audio_to_aframe(in);
+ if (!aframe && in)
+ return -1;
talloc_free(in);
- if (out->samples) {
- af_add_output_frame(af, out);
- } else {
- talloc_free(out);
- }
-
- af->delay = get_delay(s);
+ bool ok = mp_aconverter_write_input(s->converter, aframe);
+ if (!ok)
+ talloc_free(aframe);
- return 0;
-error:
- talloc_free(in);
- talloc_free(out);
- return -1;
+ return ok ? 0 : -1;
}
-static int filter(struct af_instance *af, struct mp_audio *in)
+static int filter_out(struct af_instance *af)
{
struct af_resample *s = af->priv;
-
- int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
- if (s->avrctx && !(!s->is_resampling && new_rate == s->in_rate)) {
- AVRational r = av_d2q(s->playback_speed * s->in_rate_af / s->in_rate,
- INT_MAX / 2);
- // Essentially, swr/avresample_set_compensation() does 2 things:
- // - adjust output sample rate by sample_delta/compensation_distance
- // - reset the adjustment after compensation_distance output samples
- // Increase the compensation_distance to avoid undesired reset
- // semantics - we want to keep the ratio for the whole frame we're
- // feeding it, until the next filter() call.
- int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1);
- r = (AVRational){ r.num * mult, r.den * mult };
- if (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) >= 0) {
- new_rate = s->in_rate;
- s->is_resampling = true;
- }
- }
-
- bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
- if (need_reinit && new_rate != s->in_rate) {
- // Before reconfiguring, drain the audio that is still buffered
- // in the resampler.
- filter_resample(af, NULL);
- // Reinitialize resampler.
- configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
- }
-
- return filter_resample(af, in);
+ bool eof;
+ struct mp_aframe *out = mp_aconverter_read_output(s->converter, &eof);
+ if (out)
+ af_add_output_frame(af, mp_audio_from_aframe(out));
+ talloc_free(out);
+ af->delay = mp_aconverter_get_latency(s->converter);
+ return 0;
}
static int af_open(struct af_instance *af)
@@ -557,11 +140,9 @@ static int af_open(struct af_instance *af)
af->control = control;
af->uninit = uninit;
af->filter_frame = filter;
+ af->filter_out = filter_out;
- if (s->opts.cutoff <= 0.0)
- s->opts.cutoff = af_resample_default_cutoff(s->opts.filter_size);
-
- s->reorder_buffer = mp_audio_pool_create(s);
+ s->converter = mp_aconverter_create(af->global, af->log, &s->opts);
return AF_OK;
}
@@ -574,12 +155,7 @@ const struct af_info af_info_lavrresample = {
.open = af_open,
.priv_size = sizeof(struct af_resample),
.priv_defaults = &(const struct af_resample) {
- .opts = {
- .filter_size = 16,
- .cutoff = 0.0,
- .phase_shift = 10,
- .normalize = -1,
- },
+ .opts = MP_RESAMPLE_OPTS_DEF,
.playback_speed = 1.0,
.allow_detach = 1,
},
@@ -591,7 +167,7 @@ const struct af_info af_info_lavrresample = {
OPT_FLAG("detach", allow_detach, 0),
OPT_CHOICE("normalize", opts.normalize, 0,
({"no", 0}, {"yes", 1}, {"auto", -1})),
- OPT_KEYVALUELIST("o", avopts, 0),
+ OPT_KEYVALUELIST("o", opts.avopts, 0),
{0}
},
};
diff --git a/audio/filter/af_pan.c b/audio/filter/af_pan.c
deleted file mode 100644
index b2233a7..0000000
--- a/audio/filter/af_pan.c
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <inttypes.h>
-#include <math.h>
-#include <limits.h>
-
-#include "common/common.h"
-#include "af.h"
-
-// Data for specific instances of this filter
-typedef struct af_pan_s {
- int nch; // Number of output channels; zero means same as input
- float level[AF_NCH][AF_NCH]; // Gain level for each channel
- char *matrixstr;
-} af_pan_t;
-
-static void set_channels(struct mp_audio *mpa, int num)
-{
- struct mp_chmap map;
- // "unknown" channel layouts make it easier to pass through audio data,
- // without triggering remixing.
- mp_chmap_set_unknown(&map, num);
- mp_audio_set_channels(mpa, &map);
-}
-
-static void parse_matrix(struct af_instance *af, const char *cp)
-{
- af_pan_t *s = af->priv;
- int j = 0, k = 0, n;
- while (*cp && k < AF_NCH) {
- sscanf(cp, "%f%n" , &s->level[j][k], &n);
- MP_VERBOSE(af, "Pan level from channel %i to"
- " channel %i = %f\n", k, j, s->level[j][k]);
- cp = &cp[n];
- j++;
- if (j >= s->nch) {
- j = 0;
- k++;
- }
- if (*cp != ',')
- break;
- cp++;
- }
-
-}
-
-// Initialization and runtime control
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- af_pan_t* s = af->priv;
-
- switch(cmd){
- case AF_CONTROL_REINIT:
- // Sanity check
- if (!arg)
- return AF_ERROR;
-
- af->data->rate = ((struct mp_audio*)arg)->rate;
- mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
- set_channels(af->data, s->nch ? s->nch : ((struct mp_audio*)arg)->nch);
-
- if ((af->data->format != ((struct mp_audio*)arg)->format) ||
- (af->data->bps != ((struct mp_audio*)arg)->bps)) {
- mp_audio_set_format((struct mp_audio*)arg, af->data->format);
- return AF_FALSE;
- }
- return AF_OK;
- case AF_CONTROL_SET_PAN_LEVEL: {
- int i;
- int ch = ((af_control_ext_t*)arg)->ch;
- float *level = ((af_control_ext_t*)arg)->arg;
- if (ch >= AF_NCH)
- return AF_FALSE;
- for (i = 0; i < AF_NCH; i++)
- s->level[ch][i] = level[i];
- return AF_OK;
- }
- case AF_CONTROL_SET_PAN_NOUT:
- // Reinit must be called after this function has been called
- // Sanity check
- if (((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH) {
- MP_ERR(af, "The number of output channels must be"
- " between 1 and %i. Current value is %i\n",
- AF_NCH, ((int*)arg)[0]);
- return AF_ERROR;
- }
- s->nch = ((int*)arg)[0];
- return AF_OK;
- case AF_CONTROL_SET_PAN_BALANCE: {
- float val = *(float*)arg;
- if (s->nch)
- return AF_ERROR;
- if (af->data->nch >= 2) {
- s->level[0][0] = MPMIN(1.f, 1.f - val);
- s->level[0][1] = MPMAX(0.f, val);
- s->level[1][0] = MPMAX(0.f, -val);
- s->level[1][1] = MPMIN(1.f, 1.f + val);
- }
- return AF_OK;
- }
- case AF_CONTROL_GET_PAN_BALANCE:
- if (s->nch)
- return AF_ERROR;
- *(float*)arg = s->level[0][1] - s->level[1][0];
- return AF_OK;
- case AF_CONTROL_COMMAND: {
- char **args = arg;
- if (!strcmp(args[0], "set-matrix")) {
- parse_matrix(af, args[1]);
- return CONTROL_OK;
- } else {
- return CONTROL_ERROR;
- }
- }
- }
- return AF_UNKNOWN;
-}
-
-static int filter_frame(struct af_instance *af, struct mp_audio *c)
-{
- if (!c)
- return 0;
- struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
- if (!l) {
- talloc_free(c);
- return -1;
- }
- mp_audio_copy_attributes(l, c);
-
- af_pan_t* s = af->priv; // Setup for this instance
- float *in = c->planes[0]; // Input audio data
- float *out = NULL; // Output audio data
- float *end = in+c->samples * c->nch; // End of loop
- int nchi = c->nch; // Number of input channels
- int ncho = l->nch; // Number of output channels
- register int j, k;
-
- out = l->planes[0];
- // Execute panning
- // FIXME: Too slow
- while (in < end) {
- for (j = 0; j < ncho; j++) {
- register float x = 0.0;
- register float *tin = in;
- for (k = 0; k < nchi; k++)
- x += tin[k] * s->level[j][k];
- out[j] = x;
- }
- out += ncho;
- in += nchi;
- }
-
- talloc_free(c);
- af_add_output_frame(af, l);
- return 0;
-}
-
-// Allocate memory and set function pointers
-static int af_open(struct af_instance *af)
-{
- af->control = control;
- af->filter_frame = filter_frame;
- MP_WARN(af, "This filter is deprecated. Use lavfi pan instead.\n");
- af_pan_t *s = af->priv;
- int nch = s->nch;
- if (nch && AF_OK != control(af, AF_CONTROL_SET_PAN_NOUT, &nch))
- return AF_ERROR;
-
- // Read pan values
- if (s->matrixstr)
- parse_matrix(af, s->matrixstr);
- return AF_OK;
-}
-
-#define OPT_BASE_STRUCT af_pan_t
-const struct af_info af_info_pan = {
- .info = "Panning audio filter",
- .name = "pan",
- .open = af_open,
- .priv_size = sizeof(af_pan_t),
- .options = (const struct m_option[]) {
- OPT_INTRANGE("channels", nch, 0, 0, AF_NCH),
- OPT_STRING("matrix", matrixstr, 0),
- {0}
- },
-};
diff --git a/audio/filter/af_volume.c b/audio/filter/af_volume.c
deleted file mode 100644
index 8fffc08..0000000
--- a/audio/filter/af_volume.c
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C)2002 Anders Johansson ajh@atri.curtin.edu.au
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <inttypes.h>
-#include <math.h>
-#include <limits.h>
-
-#include "common/common.h"
-#include "af.h"
-#include "demux/demux.h"
-
-struct priv {
- float level; // User-specified gain level for each channel
- float rgain; // Replaygain level
- int rgain_track; // Enable/disable track based replaygain
- int rgain_album; // Enable/disable album based replaygain
- float rgain_preamp; // Set replaygain pre-amplification
- int rgain_clip; // Enable/disable clipping prevention
- float replaygain_fallback;
- int soft; // Enable/disable soft clipping
- int fast; // Use fix-point volume control
- int detach; // Detach if gain volume is neutral
- float cfg_volume;
- int warn;
-};
-
-// Convert to gain value from dB. input <= -200dB will become 0 gain.
-static float from_dB(float in, float k, float mi, float ma)
-{
- if (in <= -200)
- return 0.0;
- return pow(10.0, MPCLAMP(in, mi, ma) / k);
-}
-
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- struct priv *s = af->priv;
-
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
-
- mp_audio_copy_config(af->data, in);
- mp_audio_force_interleaved_format(af->data);
-
- if (s->fast && af_fmt_from_planar(in->format) != AF_FORMAT_FLOAT) {
- mp_audio_set_format(af->data, AF_FORMAT_S16);
- } else {
- mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
- }
- if (af_fmt_is_planar(in->format))
- mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
- s->rgain = 1.0;
- struct replaygain_data *rg = af->replaygain_data;
- if ((s->rgain_track || s->rgain_album) && rg) {
- MP_VERBOSE(af, "Replaygain: Track=%f/%f Album=%f/%f\n",
- rg->track_gain, rg->track_peak,
- rg->album_gain, rg->album_peak);
-
- float gain, peak;
- if (s->rgain_track) {
- gain = rg->track_gain;
- peak = rg->track_peak;
- } else {
- gain = rg->album_gain;
- peak = rg->album_peak;
- }
-
- gain += s->rgain_preamp;
- s->rgain = from_dB(gain, 20.0, -200.0, 60.0);
-
- MP_VERBOSE(af, "Applying replay-gain: %f\n", s->rgain);
-
- if (!s->rgain_clip) { // clipping prevention
- s->rgain = MPMIN(s->rgain, 1.0 / peak);
- MP_VERBOSE(af, "...with clipping prevention: %f\n", s->rgain);
- }
- } else if (s->replaygain_fallback) {
- s->rgain = from_dB(s->replaygain_fallback, 20.0, -200.0, 60.0);
- MP_VERBOSE(af, "Applying fallback gain: %f\n", s->rgain);
- }
- if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
- return AF_DETACH;
- return af_test_output(af, in);
- }
- case AF_CONTROL_SET_VOLUME:
- s->level = *(float *)arg;
- MP_VERBOSE(af, "volume gain: %f\n", s->level);
- return AF_OK;
- }
- return AF_UNKNOWN;
-}
-
-static void filter_plane(struct af_instance *af, struct mp_audio *data, int p)
-{
- struct priv *s = af->priv;
-
- float level = s->level * s->rgain * from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
- int num_samples = data->samples * data->spf;
-
- if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
- int vol = 256.0 * level;
- if (vol != 256) {
- if (af_make_writeable(af, data) < 0)
- return; // oom
- int16_t *a = data->planes[p];
- for (int i = 0; i < num_samples; i++) {
- int x = (a[i] * vol) >> 8;
- a[i] = MPCLAMP(x, SHRT_MIN, SHRT_MAX);
- }
- }
- } else if (af_fmt_from_planar(af->data->format) == AF_FORMAT_FLOAT) {
- float vol = level;
- if (vol != 1.0) {
- if (af_make_writeable(af, data) < 0)
- return; // oom
- float *a = data->planes[p];
- for (int i = 0; i < num_samples; i++) {
- float x = a[i] * vol;
- a[i] = s->soft ? af_softclip(x) : MPCLAMP(x, -1.0, 1.0);
- }
- }
- }
-}
-
-static int filter(struct af_instance *af, struct mp_audio *data)
-{
- if (data) {
- for (int n = 0; n < data->num_planes; n++)
- filter_plane(af, data, n);
- af_add_output_frame(af, data);
- }
- return 0;
-}
-
-static int af_open(struct af_instance *af)
-{
- struct priv *s = af->priv;
- if (s->warn)
- MP_WARN(af, "This filter is deprecated. Use --volume directly.\n");
- af->control = control;
- af->filter_frame = filter;
- s->level = 1.0;
- return AF_OK;
-}
-
-#define OPT_BASE_STRUCT struct priv
-
-// Description of this filter
-const struct af_info af_info_volume = {
- .info = "Volume control audio filter",
- .name = "volume",
- .open = af_open,
- .priv_size = sizeof(struct priv),
- .options = (const struct m_option[]) {
- OPT_FLOATRANGE("volumedb", cfg_volume, 0, -200, 60),
- OPT_FLAG("replaygain-track", rgain_track, 0),
- OPT_FLAG("replaygain-album", rgain_album, 0),
- OPT_FLOATRANGE("replaygain-preamp", rgain_preamp, 0, -15, 15),
- OPT_FLAG("replaygain-clip", rgain_clip, 0),
- OPT_FLOATRANGE("replaygain-fallback", replaygain_fallback, 0, -200, 60),
- OPT_FLAG("softclip", soft, 0),
- OPT_FLAG("s16", fast, 0),
- OPT_FLAG("detach", detach, 0),
- OPT_FLAG("warn", warn, 0, OPTDEF_INT(1)),
- {0}
- },
-};
diff --git a/audio/format.c b/audio/format.c
index 9a0ebbe..3df11ba 100644
--- a/audio/format.c
+++ b/audio/format.c
@@ -20,7 +20,7 @@
#include <limits.h>
#include "common/common.h"
-#include "audio/filter/af.h"
+#include "format.h"
// number of bytes per sample, 0 if invalid/unknown
int af_fmt_to_bytes(int format)
diff --git a/audio/out/ao.c b/audio/out/ao.c
index c38c04c..0acd439 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <math.h>
#include <assert.h>
#include "mpv_talloc.h"
@@ -159,6 +160,7 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
if (!ao->priv)
goto error;
+ ao_set_gain(ao, 1.0f);
return ao;
error:
talloc_free(ao);
@@ -550,13 +552,6 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp)
return false;
}
-const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp)
-{
- if (!hp || !hp->ao)
- return NULL;
- return hp->ao->detected_device;
-}
-
// The return value is valid until the next call to this API.
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp)
{
@@ -645,6 +640,57 @@ void ao_print_devices(struct mpv_global *global, struct mp_log *log)
ao_hotplug_destroy(hp);
}
+void ao_set_gain(struct ao *ao, float gain)
+{
+ atomic_store(&ao->gain, gain);
+}
+
+#define MUL_GAIN_i(d, num_samples, gain, low, center, high) \
+ for (int n = 0; n < (num_samples); n++) \
+ (d)[n] = MPCLAMP( \
+ ((((int64_t)((d)[n]) - (center)) * (gain) + 128) >> 8) + (center), \
+ (low), (high))
+
+#define MUL_GAIN_f(d, num_samples, gain) \
+ for (int n = 0; n < (num_samples); n++) \
+ (d)[n] = MPCLAMP(((d)[n]) * (gain), -1.0, 1.0)
+
+static void process_plane(struct ao *ao, void *data, int num_samples)
+{
+ float gain = atomic_load_explicit(&ao->gain, memory_order_relaxed);
+ int gi = lrint(256.0 * gain);
+ if (gi == 256)
+ return;
+ switch (af_fmt_from_planar(ao->format)) {
+ case AF_FORMAT_U8:
+ MUL_GAIN_i((uint8_t *)data, num_samples, gi, 0, 128, 255);
+ break;
+ case AF_FORMAT_S16:
+ MUL_GAIN_i((int16_t *)data, num_samples, gi, INT16_MIN, 0, INT16_MAX);
+ break;
+ case AF_FORMAT_S32:
+ MUL_GAIN_i((int32_t *)data, num_samples, gi, INT32_MIN, 0, INT32_MAX);
+ break;
+ case AF_FORMAT_FLOAT:
+ MUL_GAIN_f((float *)data, num_samples, gain);
+ break;
+ case AF_FORMAT_DOUBLE:
+ MUL_GAIN_f((double *)data, num_samples, gain);
+ break;
+ default:;
+ // all other sample formats are simply not supported
+ }
+}
+
+void ao_post_process_data(struct ao *ao, void **data, int num_samples)
+{
+ bool planar = af_fmt_is_planar(ao->format);
+ int planes = planar ? ao->channels.num : 1;
+ int plane_samples = num_samples * (planar ? 1: ao->channels.num);
+ for (int n = 0; n < planes; n++)
+ process_plane(ao, data[n], plane_samples);
+}
+
static int get_conv_type(struct ao_convert_fmt *fmt)
{
if (af_fmt_to_bytes(fmt->src_fmt) * 8 == fmt->dst_bits && !fmt->pad_msb)
diff --git a/audio/out/ao.h b/audio/out/ao.h
index fbbd76f..b9df4eb 100644
--- a/audio/out/ao.h
+++ b/audio/out/ao.h
@@ -95,6 +95,7 @@ const char *ao_get_description(struct ao *ao);
bool ao_untimed(struct ao *ao);
int ao_play(struct ao *ao, void **data, int samples, int flags);
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
+void ao_set_gain(struct ao *ao, float gain);
double ao_get_delay(struct ao *ao);
int ao_get_space(struct ao *ao);
void ao_reset(struct ao *ao);
@@ -112,7 +113,6 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
void *wakeup_ctx);
void ao_hotplug_destroy(struct ao_hotplug *hp);
bool ao_hotplug_check_update(struct ao_hotplug *hp);
-const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp);
struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp);
void ao_print_devices(struct mpv_global *global, struct mp_log *log);
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index 900cbaf..39c3f03 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -12,18 +12,18 @@
*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
@@ -182,15 +182,13 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
ao_control_vol_t *vol = arg;
set_vol = vol->left / f_multi + pmin + 0.5;
- err = snd_mixer_selem_set_playback_volume
- (elem, SND_MIXER_SCHN_FRONT_LEFT, set_vol);
+ err = snd_mixer_selem_set_playback_volume(elem, 0, set_vol);
CHECK_ALSA_ERROR("Error setting left channel");
MP_DBG(ao, "left=%li, ", set_vol);
set_vol = vol->right / f_multi + pmin + 0.5;
- err = snd_mixer_selem_set_playback_volume
- (elem, SND_MIXER_SCHN_FRONT_RIGHT, set_vol);
+ err = snd_mixer_selem_set_playback_volume(elem, 1, set_vol);
CHECK_ALSA_ERROR("Error setting right channel");
MP_DBG(ao, "right=%li, pmin=%li, pmax=%li, mult=%f\n",
set_vol, pmin, pmax, f_multi);
@@ -198,11 +196,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
}
case AOCONTROL_GET_VOLUME: {
ao_control_vol_t *vol = arg;
- snd_mixer_selem_get_playback_volume
- (elem, SND_MIXER_SCHN_FRONT_LEFT, &get_vol);
+ snd_mixer_selem_get_playback_volume(elem, 0, &get_vol);
vol->left = (get_vol - pmin) * f_multi;
- snd_mixer_selem_get_playback_volume
- (elem, SND_MIXER_SCHN_FRONT_RIGHT, &get_vol);
+ snd_mixer_selem_get_playback_volume(elem, 1, &get_vol);
vol->right = (get_vol - pmin) * f_multi;
MP_DBG(ao, "left=%f, right=%f\n", vol->left, vol->right);
break;
@@ -212,11 +208,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
if (!snd_mixer_selem_has_playback_switch(elem))
goto alsa_error;
if (!snd_mixer_selem_has_playback_switch_joined(elem)) {
- snd_mixer_selem_set_playback_switch
- (elem, SND_MIXER_SCHN_FRONT_RIGHT, !*mute);
+ snd_mixer_selem_set_playback_switch(elem, 1, !*mute);
}
- snd_mixer_selem_set_playback_switch
- (elem, SND_MIXER_SCHN_FRONT_LEFT, !*mute);
+ snd_mixer_selem_set_playback_switch(elem, 0, !*mute);
break;
}
case AOCONTROL_GET_MUTE: {
@@ -224,12 +218,10 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
if (!snd_mixer_selem_has_playback_switch(elem))
goto alsa_error;
int tmp = 1;
- snd_mixer_selem_get_playback_switch
- (elem, SND_MIXER_SCHN_FRONT_LEFT, &tmp);
+ snd_mixer_selem_get_playback_switch(elem, 0, &tmp);
*mute = !tmp;
if (!snd_mixer_selem_has_playback_switch_joined(elem)) {
- snd_mixer_selem_get_playback_switch
- (elem, SND_MIXER_SCHN_FRONT_RIGHT, &tmp);
+ snd_mixer_selem_get_playback_switch(elem, 1, &tmp);
*mute &= !tmp;
}
break;
@@ -1098,10 +1090,9 @@ static int play(struct ao *ao, void **data, int samples, int flags)
if (samples == 0)
return 0;
+ ao_convert_inplace(&p->convert, data, samples);
do {
- ao_convert_inplace(&p->convert, data, samples);
-
if (af_fmt_is_planar(ao->format)) {
res = snd_pcm_writen(p->alsa, data, samples);
} else {
diff --git a/audio/out/ao_jack.c b/audio/out/ao_jack.c
index 597835c..b5413f7 100644
--- a/audio/out/ao_jack.c
+++ b/audio/out/ao_jack.c
@@ -40,6 +40,10 @@
#include <jack/jack.h>
+#if !HAVE_GPL
+#error GPL only
+#endif
+
struct jack_opts {
char *port;
char *client_name;
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
index eebd962..72413cd 100644
--- a/audio/out/ao_oss.c
+++ b/audio/out/ao_oss.c
@@ -49,6 +49,10 @@
#include "ao.h"
#include "internal.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
// Define to 0 if the device must be reopened to reset it (stop all playback,
// clear the buffer), and the device should be closed when unused.
// Define to 1 if SNDCTL_DSP_RESET should be used to reset without close.
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 28deefe..33e8a8c 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -61,9 +61,6 @@ struct ao {
// default device should be used, this is set to NULL.
char *device;
- // Device actually chosen by the AO
- char *detected_device;
-
// Application name to report to the audio API.
char *client_name;
@@ -73,6 +70,9 @@ struct ao {
// Internal events (use ao_request_reload(), ao_hotplug_event())
atomic_int events_;
+ // Float gain multiplicator
+ mp_atomic_float gain;
+
int buffer;
double def_buffer;
void *api_priv;
@@ -215,6 +215,8 @@ bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
struct ao_device_desc *e);
+void ao_post_process_data(struct ao *ao, void **data, int num_samples);
+
struct ao_convert_fmt {
int src_fmt; // source AF_FORMAT_*
int channels; // number of channels
diff --git a/audio/out/pull.c b/audio/out/pull.c
index fc8844a..a4aa538 100644
--- a/audio/out/pull.c
+++ b/audio/out/pull.c
@@ -179,6 +179,8 @@ end:
for (int n = 0; n < ao->num_planes; n++)
af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format);
+ ao_post_process_data(ao, data, samples);
+
return bytes / ao->sstride;
}
@@ -190,7 +192,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
assert(ao->api == &ao_api_pull);
struct ao_pull_state *p = ao->api_priv;
- void *ndata[MP_NUM_CHANNELS];
+ void *ndata[MP_NUM_CHANNELS] = {0};
if (!ao_need_conversion(fmt))
return ao_read_data(ao, data, samples, out_time_us);
diff --git a/audio/out/push.c b/audio/out/push.c
index c408392..b198afe 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -37,7 +37,6 @@
#include "osdep/timer.h"
#include "osdep/atomic.h"
-#include "audio/audio.h"
#include "audio/audio_buffer.h"
struct ao_push_state {
@@ -49,7 +48,8 @@ struct ao_push_state {
struct mp_audio_buffer *buffer;
- struct mp_audio *silence;
+ uint8_t *silence[MP_NUM_CHANNELS];
+ int silence_samples;
bool terminate;
bool wait_on_ao;
@@ -237,12 +237,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
flags = flags & ~AOPLAY_FINAL_CHUNK;
bool is_final = flags & AOPLAY_FINAL_CHUNK;
- struct mp_audio audio;
- mp_audio_buffer_get_format(p->buffer, &audio);
- for (int n = 0; n < ao->num_planes; n++)
- audio.planes[n] = data[n];
- audio.samples = write_samples;
- mp_audio_buffer_append(p->buffer, &audio);
+ mp_audio_buffer_append(p->buffer, data, samples);
bool got_data = write_samples > 0 || p->paused || p->final_chunk != is_final;
@@ -260,22 +255,26 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return write_samples;
}
-static void ao_get_silence(struct ao *ao, struct mp_audio *data, int size)
+static bool realloc_silence(struct ao *ao, int samples)
{
struct ao_push_state *p = ao->api_priv;
- if (!p->silence) {
- p->silence = talloc_zero(p, struct mp_audio);
- mp_audio_set_format(p->silence, ao->format);
- mp_audio_set_channels(p->silence, &ao->channels);
- p->silence->rate = ao->samplerate;
- }
- if (p->silence->samples < size) {
- mp_audio_realloc_min(p->silence, size);
- p->silence->samples = size;
- mp_audio_fill_silence(p->silence, 0, size);
+
+ if (samples <= 0 || !af_fmt_is_pcm(ao->format))
+ return false;
+
+ if (samples > p->silence_samples) {
+ talloc_free(p->silence[0]);
+
+ int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
+ p->silence[0] = talloc_size(p, bytes);
+ for (int n = 1; n < MP_NUM_CHANNELS; n++)
+ p->silence[n] = p->silence[0];
+ p->silence_samples = samples;
+
+ af_fill_silence(p->silence[0], bytes, ao->format);
}
- *data = *p->silence;
- data->samples = size;
+
+ return true;
}
// called locked
@@ -287,39 +286,42 @@ static void ao_play_data(struct ao *ao)
space = MPMAX(space, 0);
if (space % ao->period_size)
MP_ERR(ao, "Audio device reports unaligned available buffer size.\n");
- struct mp_audio data;
+ uint8_t **planes;
+ int samples;
if (play_silence) {
- ao_get_silence(ao, &data, space);
+ planes = p->silence;
+ samples = realloc_silence(ao, space) ? space : 0;
} else {
- mp_audio_buffer_peek(p->buffer, &data);
+ mp_audio_buffer_peek(p->buffer, &planes, &samples);
}
- int max = data.samples;
- if (data.samples > space)
- data.samples = space;
+ int max = samples;
+ if (samples > space)
+ samples = space;
int flags = 0;
- if (p->final_chunk && data.samples == max) {
+ if (p->final_chunk && samples == max) {
flags |= AOPLAY_FINAL_CHUNK;
} else {
- data.samples = data.samples / ao->period_size * ao->period_size;
+ samples = samples / ao->period_size * ao->period_size;
}
MP_STATS(ao, "start ao fill");
+ ao_post_process_data(ao, (void **)planes, samples);
int r = 0;
- if (data.samples)
- r = ao->driver->play(ao, data.planes, data.samples, flags);
+ if (samples)
+ r = ao->driver->play(ao, (void **)planes, samples, flags);
MP_STATS(ao, "end ao fill");
- if (r > data.samples) {
- MP_ERR(ao, "Audio device returned non-sense value.\n");
- r = data.samples;
+ if (r > samples) {
+ MP_ERR(ao, "Audio device returned nonsense value.\n");
+ r = samples;
} else if (r < 0) {
MP_ERR(ao, "Error writing audio to device.\n");
- } else if (r != data.samples) {
+ } else if (r != samples) {
MP_ERR(ao, "Audio device returned broken buffer state (sent %d samples, "
- "got %d samples, %d period%s)!\n", data.samples, r,
+ "got %d samples, %d period%s)!\n", samples, r,
ao->period_size, flags & AOPLAY_FINAL_CHUNK ? " final" : "");
}
r = MPMAX(r, 0);
// Probably can't copy the rest of the buffer due to period alignment.
- bool stuck_eof = r <= 0 && space >= max && data.samples > 0;
+ bool stuck_eof = r <= 0 && space >= max && samples > 0;
if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) {
MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
r = max;
@@ -491,17 +493,13 @@ const struct ao_driver ao_api_push = {
int ao_play_silence(struct ao *ao, int samples)
{
assert(ao->api == &ao_api_push);
- if (samples <= 0 || !af_fmt_is_pcm(ao->format) || !ao->driver->play)
+
+ struct ao_push_state *p = ao->api_priv;
+
+ if (!realloc_silence(ao, samples) || !ao->driver->play)
return 0;
- int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
- char *p = talloc_size(NULL, bytes);
- af_fill_silence(p, bytes, ao->format);
- void *tmp[MP_NUM_CHANNELS];
- for (int n = 0; n < MP_NUM_CHANNELS; n++)
- tmp[n] = p;
- int r = ao->driver->play(ao, tmp, samples, 0);
- talloc_free(p);
- return r;
+
+ return ao->driver->play(ao, (void **)p->silence, samples, 0);
}
#ifndef __MINGW32__
diff --git a/common/av_common.c b/common/av_common.c
index c91da79..65a212b 100644
--- a/common/av_common.c
+++ b/common/av_common.c
@@ -217,11 +217,6 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads
avctx->thread_count = threads;
}
-static bool is_crap(AVCodec *codec)
-{
- return !!strstr(codec->name, "_vdpau");
-}
-
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
{
AVCodec *cur = NULL;
@@ -229,7 +224,7 @@ void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
cur = av_codec_next(cur);
if (!cur)
break;
- if (av_codec_is_decoder(cur) && cur->type == type && !is_crap(cur)) {
+ if (av_codec_is_decoder(cur) && cur->type == type) {
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
cur->name, cur->long_name);
}
@@ -352,3 +347,51 @@ int mp_set_avopts(struct mp_log *log, void *avobj, char **kv)
}
return success;
}
+
+#if LIBAVUTIL_VERSION_MICRO >= 100
+AVFrameSideData *ffmpeg_garbage(AVFrame *frame,
+ enum AVFrameSideDataType type,
+ AVBufferRef *buf)
+{
+ AVFrameSideData *ret, **tmp;
+
+ if (!buf)
+ return NULL;
+
+ if (frame->nb_side_data > INT_MAX / sizeof(*frame->side_data) - 1)
+ goto fail;
+
+ tmp = av_realloc(frame->side_data,
+ (frame->nb_side_data + 1) * sizeof(*frame->side_data));
+ if (!tmp)
+ goto fail;
+ frame->side_data = tmp;
+
+ ret = av_mallocz(sizeof(*ret));
+ if (!ret)
+ goto fail;
+
+ ret->buf = buf;
+ ret->data = ret->buf->data;
+ ret->size = buf->size;
+ ret->type = type;
+
+ frame->side_data[frame->nb_side_data++] = ret;
+
+ return ret;
+fail:
+ av_buffer_unref(&buf);
+ return NULL;
+}
+#else
+AVFrameSideData *ffmpeg_garbage(AVFrame *frame,
+ enum AVFrameSideDataType type,
+ AVBufferRef *buf)
+{
+ AVFrameSideData *sd = av_frame_new_side_data(frame, type, buf->size);
+ if (sd)
+ memcpy(sd->data, buf->data, buf->size);
+ av_buffer_unref(&buf);
+ return sd;
+}
+#endif
diff --git a/common/av_common.h b/common/av_common.h
index 1d30fab..6d0c823 100644
--- a/common/av_common.h
+++ b/common/av_common.h
@@ -46,5 +46,8 @@ const char *mp_codec_from_av_codec_id(int codec_id);
void mp_set_avdict(struct AVDictionary **dict, char **kv);
void mp_avdict_print_unset(struct mp_log *log, int msgl, struct AVDictionary *d);
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv);
+AVFrameSideData *ffmpeg_garbage(AVFrame *frame,
+ enum AVFrameSideDataType type,
+ AVBufferRef *buf);
#endif
diff --git a/common/av_log.c b/common/av_log.c
index 3f07106..de600d6 100644
--- a/common/av_log.c
+++ b/common/av_log.c
@@ -5,20 +5,18 @@
*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
@@ -44,10 +42,9 @@
#include <libavdevice/avdevice.h>
#endif
-#if HAVE_IS_LIBAV
+#if HAVE_LIBAV
#include <libavresample/avresample.h>
-#endif
-#if HAVE_IS_FFMPEG
+#else
#include <libswresample/swresample.h>
#endif
@@ -67,9 +64,9 @@ static bool log_print_prefix = true;
static int av_log_level_to_mp_level(int av_level)
{
if (av_level > AV_LOG_VERBOSE)
- return MSGL_DEBUG;
+ return MSGL_TRACE;
if (av_level > AV_LOG_INFO)
- return MSGL_V;
+ return MSGL_DEBUG;
if (av_level > AV_LOG_WARNING)
return MSGL_V;
if (av_level > AV_LOG_ERROR)
@@ -199,10 +196,9 @@ bool print_libav_versions(struct mp_log *log, int v)
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
-#if HAVE_IS_LIBAV
+#if HAVE_LIBAV
{"libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()},
-#endif
-#if HAVE_IS_FFMPEG
+#else
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
#endif
};
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
index dca8f8b..2d32c56 100644
--- a/common/encode_lavc.c
+++ b/common/encode_lavc.c
@@ -793,7 +793,7 @@ int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
if (ctx->header_written <= 0)
return -1;
- MP_DBG(ctx,
+ MP_TRACE(ctx,
"write frame: stream %d ptsi %d (%f) dtsi %d (%f) size %d\n",
(int)packet->stream_index,
(int)packet->pts,
diff --git a/common/msg.c b/common/msg.c
index 623a509..b8e89be 100644
--- a/common/msg.c
+++ b/common/msg.c
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
@@ -122,7 +120,7 @@ static void update_loglevel(struct mp_log *log)
for (int n = 0; n < log->root->num_buffers; n++)
log->level = MPMAX(log->level, log->root->buffers[n]->level);
if (log->root->log_file)
- log->level = MPMAX(log->level, MSGL_V);
+ log->level = MPMAX(log->level, MSGL_DEBUG);
if (log->root->stats_file)
log->level = MPMAX(log->level, MSGL_STATS);
atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter));
@@ -287,7 +285,7 @@ static void write_log_file(struct mp_log *log, int lev, char *text)
{
struct mp_log_root *root = log->root;
- if (lev > MSGL_V || !root->log_file)
+ if (!root->log_file || lev > MPMAX(MSGL_DEBUG, log->terminal_level))
return;
fprintf(root->log_file, "[%8.3f][%c][%s] %s",
diff --git a/common/recorder.c b/common/recorder.c
index d8f937b..9970c05 100644
--- a/common/recorder.c
+++ b/common/recorder.c
@@ -160,7 +160,7 @@ struct mp_recorder *mp_recorder_create(struct mpv_global *global,
av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0);
if (avformat_write_header(priv->mux, NULL) < 0) {
- MP_ERR(priv, "Write header failed.\n");
+ MP_ERR(priv, "Writing header failed.\n");
goto error;
}
diff --git a/demux/demux.c b/demux/demux.c
index b80fbea..8c8600d 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -84,12 +84,14 @@ const demuxer_desc_t *const demuxer_list[] = {
};
struct demux_opts {
- int max_packs;
int max_bytes;
+ int max_bytes_bw;
double min_secs;
int force_seekable;
double min_secs_cache;
int access_references;
+ int seekable_cache;
+ int create_ccs;
};
#define OPT_BASE_STRUCT struct demux_opts
@@ -97,20 +99,23 @@ struct demux_opts {
const struct m_sub_options demux_conf = {
.opts = (const struct m_option[]){
OPT_DOUBLE("demuxer-readahead-secs", min_secs, M_OPT_MIN, .min = 0),
- OPT_INTRANGE("demuxer-max-packets", max_packs, 0, 0, INT_MAX,
- .deprecation_message = "use --demuxer-max-bytes"),
OPT_INTRANGE("demuxer-max-bytes", max_bytes, 0, 0, INT_MAX),
+ OPT_INTRANGE("demuxer-max-back-bytes", max_bytes_bw, 0, 0, INT_MAX),
OPT_FLAG("force-seekable", force_seekable, 0),
OPT_DOUBLE("cache-secs", min_secs_cache, M_OPT_MIN, .min = 0),
OPT_FLAG("access-references", access_references, 0),
+ OPT_CHOICE("demuxer-seekable-cache", seekable_cache, 0,
+ ({"auto", -1}, {"no", 0}, {"yes", 1})),
+ OPT_FLAG("sub-create-cc-track", create_ccs, 0),
{0}
},
.size = sizeof(struct demux_opts),
.defaults = &(const struct demux_opts){
- .max_packs = INT_MAX,
.max_bytes = 400 * 1024 * 1024,
+ .max_bytes_bw = 400 * 1024 * 1024,
.min_secs = 1.0,
- .min_secs_cache = 10.0,
+ .min_secs_cache = 10.0 * 60 * 60,
+ .seekable_cache = -1,
.access_references = 1,
},
};
@@ -150,8 +155,13 @@ struct demux_internal {
bool idle;
bool autoselect;
double min_secs;
- int max_packs;
int max_bytes;
+ int max_bytes_bw;
+ bool seekable_cache;
+
+ // At least one decoder actually requested data since init or the last seek.
+ // Do this to allow the decoder thread to select streams before starting.
+ bool reading;
// Set if we know that we are at the start of the file. This is used to
// avoid a redundant initial seek after enabling streams. We could just
@@ -164,13 +174,24 @@ struct demux_internal {
int seek_flags; // flags for next seek (if seeking==true)
double seek_pts;
- double ref_pts; // assumed player position (only for track switches)
-
double ts_offset; // timestamp offset to apply to everything
void (*run_fn)(void *); // if non-NULL, function queued to be run on
void *run_fn_arg; // the thread as run_fn(run_fn_arg)
+ // (sorted by least recent use: index 0 is least recently used)
+ struct demux_cached_range **ranges;
+ int num_ranges;
+
+ size_t total_bytes; // total sum of packet data buffered
+ size_t fw_bytes; // sum of forward packet data in current_range
+
+ // Range from which decoder is reading, and to which demuxer is appending.
+ // This is never NULL. This is always ranges[num_ranges - 1].
+ struct demux_cached_range *current_range;
+
+ double highest_av_pts; // highest non-subtitle PTS seen - for duration
+
// Cached state.
bool force_cache_update;
struct mp_tags *stream_metadata;
@@ -180,36 +201,93 @@ struct demux_internal {
char *stream_base_filename;
};
+// A continuous range of cached packets for all enabled streams.
+// (One demux_queue for each known stream.)
+struct demux_cached_range {
+ // streams[] is indexed by demux_stream->index
+ struct demux_queue **streams;
+ int num_streams;
+
+ // Computed from the stream queue's values. These fields (unlike as with
+ // demux_queue) are always either NOPTS, or fully valid.
+ double seek_start, seek_end;
+};
+
+#define MAX_INDEX_ENTRIES 16
+
+// A continuous list of cached packets for a single stream/range. There is one
+// for each stream and range. Also contains some state for use during demuxing
+// (keeping it across seeks makes it easier to resume demuxing).
+struct demux_queue {
+ struct demux_stream *ds;
+ struct demux_cached_range *range;
+
+ struct demux_packet *head;
+ struct demux_packet *tail;
+
+ struct demux_packet *next_prune_target; // cached value for faster pruning
+
+ bool correct_dts; // packet DTS is strictly monotonically increasing
+ bool correct_pos; // packet pos is strictly monotonically increasing
+ int64_t last_pos; // for determining correct_pos
+ double last_dts; // for determining correct_dts
+ double last_ts; // timestamp of the last packet added to queue
+
+ // for incrementally determining seek PTS range
+ double keyframe_pts, keyframe_end_pts;
+ struct demux_packet *keyframe_latest;
+
+ // incrementally maintained seek range, possibly invalid
+ double seek_start, seek_end;
+ double last_pruned; // timestamp of last pruned keyframe
+
+ // incomplete index to somewhat speed up seek operations
+ // the entries in index[] must be in packet queue append/removal order
+ int num_index; // valid index[] entries
+ double index_distance; // minimum keyframe distance to add index element
+ struct demux_packet *index[MAX_INDEX_ENTRIES];
+};
+
struct demux_stream {
struct demux_internal *in;
- enum stream_type type;
- // all fields are protected by in->lock
+ struct sh_stream *sh; // ds->sh->ds == ds
+ enum stream_type type; // equals to sh->type
+ int index; // equals to sh->index
+ // --- all fields are protected by in->lock
+
+ // demuxer state
bool selected; // user wants packets from this stream
- bool active; // try to keep at least 1 packet queued
+ bool eager; // try to keep at least 1 packet queued
// if false, this stream is disabled, or passively
// read (like subtitles)
- bool eof; // end of demuxed stream? (true if all buffer empty)
- bool need_refresh; // enabled mid-stream
- bool refreshing;
- bool correct_dts; // packet DTS is strictly monotonically increasing
- bool correct_pos; // packet pos is strictly monotonically increasing
- size_t packs; // number of packets in buffer
- size_t bytes; // total bytes of packets in buffer
+ bool refreshing; // finding old position after track switches
+ bool eof; // end of demuxed stream? (true if no more packets)
+
+ bool global_correct_dts;// all observed so far
+ bool global_correct_pos;
+
+ // current queue - used both for reading and demuxing (this is never NULL)
+ struct demux_queue *queue;
+
+ // reader (decoder) state (bitrate calculations are part of it because we
+ // want to return the bitrate closest to the "current position")
double base_ts; // timestamp of the last packet returned to decoder
- double last_ts; // timestamp of the last packet added to queue
double last_br_ts; // timestamp of last packet bitrate was calculated
size_t last_br_bytes; // summed packet sizes since last bitrate calculation
double bitrate;
- int64_t last_pos;
- double last_dts;
- struct demux_packet *head;
- struct demux_packet *tail;
-
- struct demux_packet *attached_picture;
+ size_t fw_packs; // number of packets in buffer (forward)
+ size_t fw_bytes; // total bytes of packets in buffer (forward)
+ struct demux_packet *reader_head; // points at current decoder position
+ bool skip_to_keyframe;
bool attached_picture_added;
+ // for refresh seeks: pos/dts of last packet returned to reader
+ int64_t last_ret_pos;
+ double last_ret_dts;
+
// for closed captions (demuxer_feed_caption)
struct sh_stream *cc;
+ bool ignore_eof; // ignore stream in underrun detection
};
// Return "a", or if that is NOPTS, return "def".
@@ -224,29 +302,315 @@ static void demuxer_sort_chapters(demuxer_t *demuxer);
static void *demux_thread(void *pctx);
static void update_cache(struct demux_internal *in);
-// called locked
-static void ds_flush(struct demux_stream *ds)
+#if 0
+// very expensive check for redundant cached queue state
+static void check_queue_consistency(struct demux_internal *in)
+{
+ size_t total_bytes = 0;
+ size_t total_fw_bytes = 0;
+
+ assert(in->current_range && in->num_ranges > 0);
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+
+ for (int n = 0; n < in->num_ranges; n++) {
+ struct demux_cached_range *range = in->ranges[n];
+
+ assert(range->num_streams == in->num_streams);
+
+ for (int i = 0; i < range->num_streams; i++) {
+ struct demux_queue *queue = range->streams[i];
+
+ assert(queue->range == range);
+
+ size_t fw_bytes = 0;
+ size_t fw_packs = 0;
+ bool is_forward = false;
+ bool kf_found = false;
+ bool npt_found = false;
+ int next_index = 0;
+ for (struct demux_packet *dp = queue->head; dp; dp = dp->next) {
+ is_forward |= dp == queue->ds->reader_head;
+ kf_found |= dp == queue->keyframe_latest;
+ npt_found |= dp == queue->next_prune_target;
+
+ size_t bytes = demux_packet_estimate_total_size(dp);
+ total_bytes += bytes;
+ if (is_forward) {
+ fw_bytes += bytes;
+ fw_packs += 1;
+ assert(range == in->current_range);
+ assert(queue->ds->queue == queue);
+ }
+
+ if (!dp->next)
+ assert(queue->tail == dp);
+
+ if (next_index < queue->num_index && queue->index[next_index] == dp)
+ next_index += 1;
+ }
+ if (!queue->head)
+ assert(!queue->tail);
+ assert(next_index == queue->num_index);
+
+ // If the queue is currently used...
+ if (queue->ds->queue == queue) {
+ // ...reader_head and others must be in the queue.
+ assert(is_forward == !!queue->ds->reader_head);
+ assert(kf_found == !!queue->keyframe_latest);
+ }
+
+ assert(npt_found == !!queue->next_prune_target);
+
+ total_fw_bytes += fw_bytes;
+
+ if (range == in->current_range) {
+ assert(queue->ds->fw_bytes == fw_bytes);
+ assert(queue->ds->fw_packs == fw_packs);
+ } else {
+ assert(fw_bytes == 0 && fw_packs == 0);
+ }
+
+ if (queue->keyframe_latest)
+ assert(queue->keyframe_latest->keyframe);
+ }
+ }
+
+ assert(in->total_bytes == total_bytes);
+ assert(in->fw_bytes == total_fw_bytes);
+}
+#endif
+
+static void recompute_buffers(struct demux_stream *ds)
+{
+ ds->fw_packs = 0;
+ ds->fw_bytes = 0;
+
+ for (struct demux_packet *dp = ds->reader_head; dp; dp = dp->next) {
+ ds->fw_bytes += demux_packet_estimate_total_size(dp);
+ ds->fw_packs++;
+ }
+}
+
+// (this doesn't do most required things for a switch, like updating ds->queue)
+static void set_current_range(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ in->current_range = range;
+
+ // Move to in->ranges[in->num_ranges-1] (for LRU sorting/invariant)
+ for (int n = 0; n < in->num_ranges; n++) {
+ if (in->ranges[n] == range) {
+ MP_TARRAY_REMOVE_AT(in->ranges, in->num_ranges, n);
+ break;
+ }
+ }
+ MP_TARRAY_APPEND(in, in->ranges, in->num_ranges, range);
+}
+
+// Refresh range->seek_start/end.
+static void update_seek_ranges(struct demux_cached_range *range)
{
- demux_packet_t *dp = ds->head;
+ range->seek_start = range->seek_end = MP_NOPTS_VALUE;
+
+ for (int n = 0; n < range->num_streams; n++) {
+ struct demux_queue *queue = range->streams[n];
+
+ if (queue->ds->selected && queue->ds->eager) {
+ range->seek_start = MP_PTS_MAX(range->seek_start, queue->seek_start);
+ range->seek_end = MP_PTS_MIN(range->seek_end, queue->seek_end);
+
+ if (queue->seek_start >= queue->seek_end) {
+ range->seek_start = range->seek_end = MP_NOPTS_VALUE;
+ break;
+ }
+ }
+ }
+
+ // Sparse stream behavior is not very clearly defined, but usually we don't
+ // want it to restrict the range of other streams, unless
+ // This is incorrect in any of these cases:
+ // - sparse streams only (it's unknown how to determine an accurate range)
+ // - if sparse streams have non-keyframe packets (we set queue->last_pruned
+ // to the start of the pruned keyframe range - we'd need the end or so)
+ // We also assume that ds->eager equals to a stream being sparse (usually
+ // true, except if only sparse streams are selected).
+ // We also rely on the fact that the demuxer position will always be ahead
+ // of the seek_end for audio/video, because they need to prefetch at least
+ // 1 packet to detect the end of a keyframe range. This means that we're
+ // relatively guaranteed to have all sparse (subtitle) packets within the
+ // seekable range.
+ for (int n = 0; n < range->num_streams; n++) {
+ struct demux_queue *queue = range->streams[n];
+ if (queue->ds->selected && !queue->ds->eager &&
+ queue->last_pruned != MP_NOPTS_VALUE)
+ {
+ // (last_pruned is _exclusive_ to the seekable range, so add a small
+ // value to exclude it from the valid range.)
+ range->seek_start =
+ MP_PTS_MAX(range->seek_start, queue->last_pruned + 0.1);
+ }
+ }
+
+ if (range->seek_start >= range->seek_end)
+ range->seek_start = range->seek_end = MP_NOPTS_VALUE;
+}
+
+// Remove queue->head from the queue. Does not update in->fw_bytes/in->fw_packs.
+static void remove_head_packet(struct demux_queue *queue)
+{
+ struct demux_packet *dp = queue->head;
+
+ assert(queue->ds->reader_head != dp);
+ if (queue->next_prune_target == dp)
+ queue->next_prune_target = NULL;
+ if (queue->keyframe_latest == dp)
+ queue->keyframe_latest = NULL;
+
+ queue->ds->in->total_bytes -= demux_packet_estimate_total_size(dp);
+
+ if (queue->num_index && queue->index[0] == dp)
+ MP_TARRAY_REMOVE_AT(queue->index, queue->num_index, 0);
+
+ queue->head = dp->next;
+ if (!queue->head)
+ queue->tail = NULL;
+
+ talloc_free(dp);
+}
+
+static void clear_queue(struct demux_queue *queue)
+{
+ struct demux_stream *ds = queue->ds;
+ struct demux_internal *in = ds->in;
+
+ struct demux_packet *dp = queue->head;
while (dp) {
- demux_packet_t *dn = dp->next;
- free_demux_packet(dp);
+ struct demux_packet *dn = dp->next;
+ in->total_bytes -= demux_packet_estimate_total_size(dp);
+ assert(ds->reader_head != dp);
+ talloc_free(dp);
dp = dn;
}
- ds->head = ds->tail = NULL;
- ds->packs = 0;
- ds->bytes = 0;
- ds->last_ts = ds->base_ts = ds->last_br_ts = MP_NOPTS_VALUE;
+ queue->head = queue->tail = NULL;
+ queue->next_prune_target = NULL;
+ queue->keyframe_latest = NULL;
+ queue->seek_start = queue->seek_end = queue->last_pruned = MP_NOPTS_VALUE;
+
+ queue->num_index = 0;
+ queue->index_distance = 1.0;
+
+ queue->correct_dts = queue->correct_pos = true;
+ queue->last_pos = -1;
+ queue->last_ts = queue->last_dts = MP_NOPTS_VALUE;
+ queue->keyframe_latest = NULL;
+ queue->keyframe_pts = queue->keyframe_end_pts = MP_NOPTS_VALUE;
+}
+
+static void clear_cached_range(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ for (int n = 0; n < range->num_streams; n++)
+ clear_queue(range->streams[n]);
+ update_seek_ranges(range);
+}
+
+// Remove ranges with no data (except in->current_range). Also remove excessive
+// ranges.
+static void free_empty_cached_ranges(struct demux_internal *in)
+{
+ assert(in->current_range && in->num_ranges > 0);
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+
+ while (1) {
+ struct demux_cached_range *worst = NULL;
+
+ for (int n = in->num_ranges - 2; n >= 0; n--) {
+ struct demux_cached_range *range = in->ranges[n];
+ if (range->seek_start == MP_NOPTS_VALUE || !in->seekable_cache) {
+ clear_cached_range(in, range);
+ MP_TARRAY_REMOVE_AT(in->ranges, in->num_ranges, n);
+ } else {
+ if (!worst || (range->seek_end - range->seek_start <
+ worst->seek_end - worst->seek_start))
+ worst = range;
+ }
+ }
+
+ if (in->num_ranges <= MAX_SEEK_RANGES)
+ break;
+
+ clear_cached_range(in, worst);
+ }
+}
+
+static void ds_clear_reader_queue_state(struct demux_stream *ds)
+{
+ ds->in->fw_bytes -= ds->fw_bytes;
+ ds->reader_head = NULL;
+ ds->fw_bytes = 0;
+ ds->fw_packs = 0;
+ ds->eof = false;
+}
+
+static void ds_clear_reader_state(struct demux_stream *ds)
+{
+ ds_clear_reader_queue_state(ds);
+
+ ds->base_ts = ds->last_br_ts = MP_NOPTS_VALUE;
ds->last_br_bytes = 0;
ds->bitrate = -1;
+ ds->skip_to_keyframe = false;
+ ds->attached_picture_added = false;
+ ds->last_ret_pos = -1;
+ ds->last_ret_dts = MP_NOPTS_VALUE;
+}
+
+static void update_stream_selection_state(struct demux_internal *in,
+ struct demux_stream *ds)
+{
ds->eof = false;
- ds->active = false;
ds->refreshing = false;
- ds->need_refresh = false;
- ds->last_pos = -1;
- ds->last_dts = MP_NOPTS_VALUE;
- ds->correct_dts = ds->correct_pos = true;
- ds->attached_picture_added = false;
+
+ ds_clear_reader_state(ds);
+
+ // We still have to go over the whole stream list to update ds->eager for
+ // other streams too, because they depend on other stream's selections.
+
+ bool any_av_streams = false;
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *s = in->streams[n]->ds;
+
+ s->eager = s->selected && !s->sh->attached_picture;
+ if (s->eager)
+ any_av_streams |= s->type != STREAM_SUB;
+ }
+
+ // Subtitles are only eagerly read if there are no other eagerly read
+ // streams.
+ if (any_av_streams) {
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *s = in->streams[n]->ds;
+
+ if (s->type == STREAM_SUB)
+ s->eager = false;
+ }
+ }
+
+ // Make sure any stream reselection or addition is reflected in the seek
+ // ranges, and also get rid of data that is not needed anymore (or
+ // rather, which can't be kept consistent). This has to happen after we've
+ // updated all the subtle state (like s->eager).
+ for (int n = 0; n < in->num_ranges; n++) {
+ struct demux_cached_range *range = in->ranges[n];
+
+ if (!ds->selected)
+ clear_queue(range->streams[ds->index]);
+
+ update_seek_ranges(range);
+ }
+
+ free_empty_cached_ranges(in);
}
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
@@ -257,6 +621,23 @@ void demux_set_ts_offset(struct demuxer *demuxer, double offset)
pthread_mutex_unlock(&in->lock);
}
+static void add_missing_streams(struct demux_internal *in,
+ struct demux_cached_range *range)
+{
+ for (int n = range->num_streams; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ struct demux_queue *queue = talloc_ptrtype(range, queue);
+ *queue = (struct demux_queue){
+ .ds = ds,
+ .range = range,
+ };
+ clear_queue(queue);
+ MP_TARRAY_APPEND(range, range->streams, range->num_streams, queue);
+ assert(range->streams[ds->index] == queue);
+ }
+}
+
// Allocate a new sh_stream of the given type. It either has to be released
// with talloc_free(), or added to a demuxer with demux_add_sh_stream(). You
// cannot add or read packets from the stream before it has been added.
@@ -278,26 +659,27 @@ struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
// Add a new sh_stream to the demuxer. Note that as soon as the stream has been
// added, it must be immutable, and must not be released (this will happen when
// the demuxer is destroyed).
-void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
+static void demux_add_sh_stream_locked(struct demux_internal *in,
+ struct sh_stream *sh)
{
- struct demux_internal *in = demuxer->in;
- pthread_mutex_lock(&in->lock);
-
assert(!sh->ds); // must not be added yet
+ sh->index = in->num_streams;
+
sh->ds = talloc(sh, struct demux_stream);
*sh->ds = (struct demux_stream) {
.in = in,
+ .sh = sh,
.type = sh->type,
+ .index = sh->index,
.selected = in->autoselect,
+ .global_correct_dts = true,
+ .global_correct_pos = true,
};
if (!sh->codec->codec)
sh->codec->codec = "";
- sh->ds->attached_picture = sh->attached_picture;
-
- sh->index = in->num_streams;
if (sh->ff_index < 0)
sh->ff_index = sh->index;
if (sh->demuxer_id < 0) {
@@ -309,10 +691,26 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
}
MP_TARRAY_APPEND(in, in->streams, in->num_streams, sh);
+ assert(in->streams[sh->index] == sh);
+
+ for (int n = 0; n < in->num_ranges; n++)
+ add_missing_streams(in, in->ranges[n]);
+
+ sh->ds->queue = in->current_range->streams[sh->ds->index];
+
+ update_stream_selection_state(in, sh->ds);
in->events |= DEMUX_EVENT_STREAMS;
if (in->wakeup_cb)
in->wakeup_cb(in->wakeup_cb_ctx);
+}
+
+// For demuxer implementations only.
+void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
+{
+ struct demux_internal *in = demuxer->in;
+ pthread_mutex_lock(&in->lock);
+ demux_add_sh_stream_locked(in, sh);
pthread_mutex_unlock(&in->lock);
}
@@ -378,10 +776,12 @@ void free_demuxer(demuxer_t *demuxer)
if (demuxer->desc->close)
demuxer->desc->close(in->d_thread);
- for (int n = in->num_streams - 1; n >= 0; n--) {
- ds_flush(in->streams[n]->ds);
+
+ demux_flush(demuxer);
+ assert(in->total_bytes == 0);
+
+ for (int n = 0; n < in->num_streams; n++)
talloc_free(in->streams[n]);
- }
pthread_mutex_destroy(&in->lock);
pthread_cond_destroy(&in->wakeup);
talloc_free(demuxer);
@@ -445,132 +845,341 @@ const char *stream_type_name(enum stream_type type)
}
}
-void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
+static struct sh_stream *demuxer_get_cc_track_locked(struct sh_stream *stream)
{
- struct demuxer *demuxer = stream->ds->in->d_thread;
struct sh_stream *sh = stream->ds->cc;
if (!sh) {
sh = demux_alloc_sh_stream(STREAM_SUB);
- if (!sh) {
- talloc_free(dp);
- return;
- }
+ if (!sh)
+ return NULL;
sh->codec->codec = "eia_608";
+ sh->default_track = true;
stream->ds->cc = sh;
- demux_add_sh_stream(demuxer, sh);
+ demux_add_sh_stream_locked(stream->ds->in, sh);
+ sh->ds->ignore_eof = true;
+ }
+
+ return sh;
+}
+
+void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
+{
+ struct demux_internal *in = stream->ds->in;
+
+ pthread_mutex_lock(&in->lock);
+ struct sh_stream *sh = demuxer_get_cc_track_locked(stream);
+ if (!sh) {
+ pthread_mutex_unlock(&in->lock);
+ talloc_free(dp);
+ return;
}
- dp->pts = MP_ADD_PTS(dp->pts, -stream->ds->in->ts_offset);
- dp->dts = MP_ADD_PTS(dp->dts, -stream->ds->in->ts_offset);
+ dp->pts = MP_ADD_PTS(dp->pts, -in->ts_offset);
+ dp->dts = MP_ADD_PTS(dp->dts, -in->ts_offset);
+ pthread_mutex_unlock(&in->lock);
+
demux_add_packet(sh, dp);
}
-// An obscure mechanism to get stream switching to be executed faster.
-// On a switch, it seeks back, and then grabs all packets that were
-// "missing" from the packet queue of the newly selected stream.
-// Returns MP_NOPTS_VALUE if no seek should happen.
-static double get_refresh_seek_pts(struct demux_internal *in)
+// Add the keyframe to the end of the index. Not all packets are actually added.
+static void add_index_entry(struct demux_queue *queue, struct demux_packet *dp)
{
- struct demuxer *demux = in->d_thread;
+ assert(dp->keyframe && dp->kf_seek_pts != MP_NOPTS_VALUE);
- double start_ts = in->ref_pts;
- bool needed = false;
- bool normal_seek = true;
- bool refresh_possible = true;
+ if (queue->num_index) {
+ double prev = queue->index[queue->num_index - 1]->kf_seek_pts;
+ if (dp->kf_seek_pts < prev + queue->index_distance)
+ return;
+ }
+
+ if (queue->num_index == MAX_INDEX_ENTRIES) {
+ for (int n = 0; n < MAX_INDEX_ENTRIES / 2; n++)
+ queue->index[n] = queue->index[n * 2];
+ queue->num_index = MAX_INDEX_ENTRIES / 2;
+ queue->index_distance *= 2;
+ }
+
+ queue->index[queue->num_index++] = dp;
+}
+
+// Check whether the next range in the list is, and if it appears to overlap,
+// try joining it into a single range.
+static void attempt_range_joining(struct demux_internal *in)
+{
+ struct demux_cached_range *next = NULL;
+ double next_dist = INFINITY;
+
+ assert(in->current_range && in->num_ranges > 0);
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+
+ for (int n = 0; n < in->num_ranges - 1; n++) {
+ struct demux_cached_range *range = in->ranges[n];
+
+ if (in->current_range->seek_start <= range->seek_start) {
+ // This uses ">" to get some non-0 overlap.
+ double dist = in->current_range->seek_end - range->seek_start;
+ if (dist > 0 && dist < next_dist) {
+ next = range;
+ next_dist = dist;
+ }
+ }
+ }
+
+ if (!next)
+ return;
+
+ MP_VERBOSE(in, "going to join ranges %f-%f + %f-%f\n",
+ in->current_range->seek_start, in->current_range->seek_end,
+ next->seek_start, next->seek_end);
+
+ // Try to find a join point, where packets obviously overlap. (It would be
+ // better and faster to do this incrementally, but probably too complex.)
+ // The current range can overlap arbitrarily with the next one, not only by
+ // by the seek overlap, but for arbitrary packet readahead as well.
+ // We also drop the overlapping packets (if joining fails, we discard the
+ // entire next range anyway, so this does no harm).
for (int n = 0; n < in->num_streams; n++) {
struct demux_stream *ds = in->streams[n]->ds;
- if (!ds->selected)
- continue;
+ struct demux_queue *q1 = in->current_range->streams[n];
+ struct demux_queue *q2 = next->streams[n];
- if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
- start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
+ if (!ds->global_correct_pos && !ds->global_correct_dts) {
+ MP_WARN(in, "stream %d: ranges unjoinable\n", n);
+ goto failed;
+ }
- needed |= ds->need_refresh;
- // If there were no other streams selected, we can use a normal seek.
- normal_seek &= ds->need_refresh;
- ds->need_refresh = false;
+ struct demux_packet *end = q1->tail;
+ bool join_point_found = !end; // no packets yet -> joining will work
+ if (end) {
+ while (q2->head) {
+ struct demux_packet *dp = q2->head;
+
+ // Some weird corner-case. We'd have to search the equivalent
+ // packet in q1 to update it correctly. Better just give up.
+ if (dp == q2->keyframe_latest) {
+ MP_WARN(in, "stream %d: not enough keyframes\n", n);
+ goto failed;
+ }
- refresh_possible &= ds->correct_dts || ds->correct_pos;
- }
+ if ((ds->global_correct_dts && dp->dts == end->dts) ||
+ (ds->global_correct_pos && dp->pos == end->pos))
+ {
+ // Do some additional checks as a (imperfect) sanity check
+ // in case pos/dts are not "correct" across the ranges (we
+ // never actually check that).
+ if (dp->dts != end->dts || dp->pos != end->pos ||
+ dp->pts != end->pts || dp->len != end->len)
+ {
+ MP_WARN(in, "stream %d: weird demuxer behavior\n", n);
+ goto failed;
+ }
+
+ remove_head_packet(q2);
+ join_point_found = true;
+ break;
+ }
- if (!needed || start_ts == MP_NOPTS_VALUE || !demux->desc->seek ||
- !demux->seekable || demux->partially_seekable)
- return MP_NOPTS_VALUE;
+ // This happens if the next range misses the end packet. For
+ // normal streams (ds->eager==true), this is a failure to find
+ // an overlap. For subtitles, this can mean the current_range
+ // has a subtitle somewhere before the end of its range, and
+ // next has another subtitle somewhere after the start of its
+ // range.
+ if ((ds->global_correct_dts && dp->dts > end->dts) ||
+ (ds->global_correct_pos && dp->pos > end->pos))
+ break;
- if (normal_seek)
- return start_ts;
+ remove_head_packet(q2);
+ }
+ }
- if (!refresh_possible) {
- MP_VERBOSE(in, "can't issue refresh seek\n");
- return MP_NOPTS_VALUE;
+ // For enabled non-sparse streams, always require an overlap packet.
+ if (ds->eager && !join_point_found) {
+ MP_WARN(in, "stream %d: no joint point found\n", n);
+ goto failed;
+ }
}
+ // Actually join the ranges. Now that we think it will work, mutate the
+ // data associated with the current range.
+
+ in->fw_bytes = 0;
+
for (int n = 0; n < in->num_streams; n++) {
+ struct demux_queue *q1 = in->current_range->streams[n];
+ struct demux_queue *q2 = next->streams[n];
+
struct demux_stream *ds = in->streams[n]->ds;
- // Streams which didn't have any packets yet will return all packets,
- // other streams return packets only starting from the last position.
- if (ds->last_pos != -1 || ds->last_dts != MP_NOPTS_VALUE)
- ds->refreshing |= ds->selected;
+
+ if (q2->head) {
+ if (q1->head) {
+ q1->tail->next = q2->head;
+ } else {
+ q1->head = q2->head;
+ }
+ q1->tail = q2->tail;
+ }
+
+ q1->seek_end = q2->seek_end;
+ q1->correct_dts &= q2->correct_dts;
+ q1->correct_pos &= q2->correct_pos;
+ q1->last_pos = q2->last_pos;
+ q1->last_dts = q2->last_dts;
+ q1->last_ts = q2->last_ts;
+ q1->keyframe_pts = q2->keyframe_pts;
+ q1->keyframe_end_pts = q2->keyframe_end_pts;
+ q1->keyframe_latest = q2->keyframe_latest;
+
+ q2->head = q2->tail = NULL;
+ q2->next_prune_target = NULL;
+ q2->keyframe_latest = NULL;
+
+ for (int i = 0; i < q2->num_index; i++)
+ add_index_entry(q1, q2->index[i]);
+ q2->num_index = 0;
+
+ recompute_buffers(ds);
+ in->fw_bytes += ds->fw_bytes;
+
+ // For moving demuxer position.
+ ds->refreshing = ds->selected;
+ }
+
+ update_seek_ranges(in->current_range);
+
+ // Move demuxing position to after the current range.
+ in->seeking = true;
+ in->seek_flags = SEEK_HR;
+ in->seek_pts = next->seek_end - 1.0;
+
+ MP_VERBOSE(in, "ranges joined!\n");
+
+failed:
+ clear_cached_range(in, next);
+ free_empty_cached_ranges(in);
+}
+
+// Determine seekable range when a packet is added. If dp==NULL, treat it as
+// EOF (i.e. closes the current block).
+// This has to deal with a number of corner cases, such as demuxers potentially
+// starting output at non-keyframes.
+// Can join seek ranges, which messes with in->current_range and all.
+static void adjust_seek_range_on_packet(struct demux_stream *ds,
+ struct demux_packet *dp)
+{
+ struct demux_queue *queue = ds->queue;
+ bool attempt_range_join = false;
+
+ if (!ds->in->seekable_cache)
+ return;
+
+ if (!dp || dp->keyframe) {
+ if (queue->keyframe_latest) {
+ queue->keyframe_latest->kf_seek_pts = queue->keyframe_pts;
+ double old_end = queue->range->seek_end;
+ if (queue->seek_start == MP_NOPTS_VALUE)
+ queue->seek_start = queue->keyframe_pts;
+ if (queue->keyframe_end_pts != MP_NOPTS_VALUE)
+ queue->seek_end = queue->keyframe_end_pts;
+ update_seek_ranges(queue->range);
+ attempt_range_join = queue->range->seek_end > old_end;
+ if (queue->keyframe_latest->kf_seek_pts != MP_NOPTS_VALUE)
+ add_index_entry(queue, queue->keyframe_latest);
+ }
+ queue->keyframe_latest = dp;
+ queue->keyframe_pts = queue->keyframe_end_pts = MP_NOPTS_VALUE;
}
- // Seek back to player's current position, with a small offset added.
- return start_ts - 1.0;
+ if (dp) {
+ dp->kf_seek_pts = MP_NOPTS_VALUE;
+
+ double ts = PTS_OR_DEF(dp->pts, dp->dts);
+ if (dp->segmented && (ts < dp->start || ts > dp->end))
+ ts = MP_NOPTS_VALUE;
+
+ queue->keyframe_pts = MP_PTS_MIN(queue->keyframe_pts, ts);
+ queue->keyframe_end_pts = MP_PTS_MAX(queue->keyframe_end_pts, ts);
+ }
+
+ if (attempt_range_join)
+ attempt_range_joining(ds->in);
}
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
{
struct demux_stream *ds = stream ? stream->ds : NULL;
- if (!dp || !ds) {
+ if (!dp || !dp->len || !ds) {
talloc_free(dp);
return;
}
struct demux_internal *in = ds->in;
pthread_mutex_lock(&in->lock);
- bool drop = ds->refreshing;
- if (ds->refreshing) {
+ struct demux_queue *queue = ds->queue;
+
+ bool drop = !ds->selected || in->seeking;
+ if (!drop && ds->refreshing) {
// Resume reading once the old position was reached (i.e. we start
// returning packets where we left off before the refresh).
// If it's the same position, drop, but continue normally next time.
- if (ds->correct_dts) {
- ds->refreshing = dp->dts < ds->last_dts;
- } else if (ds->correct_pos) {
- ds->refreshing = dp->pos < ds->last_pos;
+ if (queue->correct_dts) {
+ ds->refreshing = dp->dts < queue->last_dts;
+ } else if (queue->correct_pos) {
+ ds->refreshing = dp->pos < queue->last_pos;
} else {
ds->refreshing = false; // should not happen
+ MP_WARN(in, "stream %d: demux refreshing failed\n", ds->index);
}
+ drop = true;
}
- if (!ds->selected || ds->need_refresh || in->seeking || drop) {
+ if (drop) {
pthread_mutex_unlock(&in->lock);
talloc_free(dp);
return;
}
- ds->correct_pos &= dp->pos >= 0 && dp->pos > ds->last_pos;
- ds->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > ds->last_dts;
- ds->last_pos = dp->pos;
- ds->last_dts = dp->dts;
+ queue->correct_pos &= dp->pos >= 0 && dp->pos > queue->last_pos;
+ queue->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > queue->last_dts;
+ queue->last_pos = dp->pos;
+ queue->last_dts = dp->dts;
+ ds->global_correct_pos &= queue->correct_pos;
+ ds->global_correct_dts &= queue->correct_dts;
dp->stream = stream->index;
dp->next = NULL;
- ds->packs++;
- ds->bytes += demux_packet_estimate_total_size(dp);
- if (ds->tail) {
+ // (keep in mind that even if the reader went out of data, the queue is not
+ // necessarily empty due to the backbuffer)
+ if (!ds->reader_head && (!ds->skip_to_keyframe || dp->keyframe)) {
+ ds->reader_head = dp;
+ ds->skip_to_keyframe = false;
+ }
+
+ size_t bytes = demux_packet_estimate_total_size(dp);
+ ds->in->total_bytes += bytes;
+ if (ds->reader_head) {
+ ds->fw_packs++;
+ ds->fw_bytes += bytes;
+ in->fw_bytes += bytes;
+ }
+
+ if (queue->tail) {
// next packet in stream
- ds->tail->next = dp;
- ds->tail = dp;
+ queue->tail->next = dp;
+ queue->tail = dp;
} else {
// first packet in stream
- ds->head = ds->tail = dp;
+ queue->head = queue->tail = dp;
}
- // obviously not true anymore
- ds->eof = false;
- in->last_eof = in->eof = false;
+ if (!ds->ignore_eof) {
+ // obviously not true anymore
+ ds->eof = false;
+ in->last_eof = in->eof = false;
+ }
// For video, PTS determination is not trivial, but for other media types
// distinguishing PTS and DTS is not useful.
@@ -578,16 +1187,38 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
dp->pts = dp->dts;
double ts = dp->dts == MP_NOPTS_VALUE ? dp->pts : dp->dts;
- if (ts != MP_NOPTS_VALUE && (ts > ds->last_ts || ts + 10 < ds->last_ts))
- ds->last_ts = ts;
+ if (dp->segmented)
+ ts = MP_PTS_MIN(ts, dp->end);
+ if (ts != MP_NOPTS_VALUE && (ts > queue->last_ts || ts + 10 < queue->last_ts))
+ queue->last_ts = ts;
if (ds->base_ts == MP_NOPTS_VALUE)
- ds->base_ts = ds->last_ts;
-
- MP_DBG(in, "append packet to %s: size=%d pts=%f dts=%f pos=%"PRIi64" "
- "[num=%zd size=%zd]\n", stream_type_name(stream->type),
- dp->len, dp->pts, dp->dts, dp->pos, ds->packs, ds->bytes);
+ ds->base_ts = queue->last_ts;
+
+ MP_TRACE(in, "append packet to %s: size=%d pts=%f dts=%f pos=%"PRIi64" "
+ "[num=%zd size=%zd]\n", stream_type_name(stream->type),
+ dp->len, dp->pts, dp->dts, dp->pos, ds->fw_packs, ds->fw_bytes);
+
+ adjust_seek_range_on_packet(ds, dp);
+
+ // Possible update duration based on highest TS demuxed (but ignore subs).
+ if (stream->type != STREAM_SUB) {
+ if (dp->segmented)
+ ts = MP_PTS_MIN(ts, dp->end);
+ if (ts > in->highest_av_pts) {
+ in->highest_av_pts = ts;
+ double duration = in->highest_av_pts - in->d_thread->start_time;
+ if (duration > in->d_thread->duration) {
+ in->d_thread->duration = duration;
+ // (Don't wakeup like like demux_changed(), would be too noisy.)
+ in->d_thread->events |= DEMUX_EVENT_DURATION;
+ in->d_buffer->duration = duration;
+ in->d_buffer->events |= DEMUX_EVENT_DURATION;
+ }
+ }
+ }
- if (ds->in->wakeup_cb && !ds->head->next)
+ // Wake up if this was the first packet after start/possible underrun.
+ if (ds->in->wakeup_cb && ds->reader_head && !ds->reader_head->next)
ds->in->wakeup_cb(ds->in->wakeup_cb_ctx);
pthread_cond_signal(&in->wakeup);
pthread_mutex_unlock(&in->lock);
@@ -599,53 +1230,56 @@ static bool read_packet(struct demux_internal *in)
in->eof = false;
in->idle = true;
+ if (!in->reading)
+ return false;
+
// Check if we need to read a new packet. We do this if all queues are below
// the minimum, or if a stream explicitly needs new packets. Also includes
// safe-guards against packet queue overflow.
- bool active = false, read_more = false;
- size_t packs = 0, bytes = 0;
+ bool read_more = false, prefetch_more = false, refresh_more = false;
for (int n = 0; n < in->num_streams; n++) {
struct demux_stream *ds = in->streams[n]->ds;
- active |= ds->active;
- read_more |= (ds->active && !ds->head) || ds->refreshing;
- packs += ds->packs;
- bytes += ds->bytes;
- if (ds->active && ds->last_ts != MP_NOPTS_VALUE && in->min_secs > 0 &&
- ds->last_ts >= ds->base_ts)
- read_more |= ds->last_ts - ds->base_ts < in->min_secs;
- }
- MP_DBG(in, "packets=%zd, bytes=%zd, active=%d, more=%d\n",
- packs, bytes, active, read_more);
- if (packs >= in->max_packs || bytes >= in->max_bytes) {
+ read_more |= ds->eager && !ds->reader_head;
+ refresh_more |= ds->refreshing;
+ if (ds->eager && ds->queue->last_ts != MP_NOPTS_VALUE &&
+ in->min_secs > 0 && ds->base_ts != MP_NOPTS_VALUE &&
+ ds->queue->last_ts >= ds->base_ts)
+ prefetch_more |= ds->queue->last_ts - ds->base_ts < in->min_secs;
+ }
+ MP_TRACE(in, "bytes=%zd, read_more=%d prefetch_more=%d, refresh_more=%d\n",
+ in->fw_bytes, read_more, prefetch_more, refresh_more);
+ if (in->fw_bytes >= in->max_bytes) {
+ // if we hit the limit just by prefetching, simply stop prefetching
+ if (!read_more)
+ return false;
if (!in->warned_queue_overflow) {
in->warned_queue_overflow = true;
MP_WARN(in, "Too many packets in the demuxer packet queues:\n");
for (int n = 0; n < in->num_streams; n++) {
struct demux_stream *ds = in->streams[n]->ds;
if (ds->selected) {
- MP_WARN(in, " %s/%d: %zd packets, %zd bytes\n",
- stream_type_name(ds->type), n, ds->packs, ds->bytes);
+ MP_WARN(in, " %s/%d: %zd packets, %zd bytes%s%s\n",
+ stream_type_name(ds->type), n,
+ ds->fw_packs, ds->fw_bytes,
+ ds->eager ? "" : " (lazy)",
+ ds->refreshing ? " (refreshing)" : "");
}
}
}
for (int n = 0; n < in->num_streams; n++) {
struct demux_stream *ds = in->streams[n]->ds;
- bool eof = !ds->head;
+ bool eof = !ds->reader_head;
if (eof && !ds->eof) {
if (in->wakeup_cb)
in->wakeup_cb(in->wakeup_cb_ctx);
+ pthread_cond_signal(&in->wakeup);
}
ds->eof |= eof;
}
- pthread_cond_signal(&in->wakeup);
return false;
}
- double seek_pts = get_refresh_seek_pts(in);
- bool refresh_seek = seek_pts != MP_NOPTS_VALUE;
- read_more |= refresh_seek;
-
- if (!read_more)
+ if (!read_more && !prefetch_more && !refresh_more)
return false;
// Actually read a packet. Drop the lock while doing so, because waiting
@@ -656,11 +1290,6 @@ static bool read_packet(struct demux_internal *in)
struct demuxer *demux = in->d_thread;
- if (refresh_seek) {
- MP_VERBOSE(in, "refresh seek to %f\n", seek_pts);
- demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
- }
-
bool eof = true;
if (demux->desc->fill_buffer && !demux_cancel_test(demux))
eof = demux->desc->fill_buffer(demux) <= 0;
@@ -670,8 +1299,12 @@ static bool read_packet(struct demux_internal *in)
if (!in->seeking) {
if (eof) {
- for (int n = 0; n < in->num_streams; n++)
- in->streams[n]->ds->eof = true;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ if (!ds->eof)
+ adjust_seek_range_on_packet(ds, NULL);
+ ds->eof = true;
+ }
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
if (!in->last_eof) {
if (in->wakeup_cb)
@@ -685,6 +1318,84 @@ static bool read_packet(struct demux_internal *in)
return true;
}
+static void prune_old_packets(struct demux_internal *in)
+{
+ assert(in->current_range == in->ranges[in->num_ranges - 1]);
+
+ // It's not clear what the ideal way to prune old packets is. For now, we
+ // prune the oldest packet runs, as long as the total cache amount is too
+ // big.
+ size_t max_bytes = in->seekable_cache ? in->max_bytes_bw : 0;
+ while (in->total_bytes - in->fw_bytes > max_bytes) {
+ // (Start from least recently used range.)
+ struct demux_cached_range *range = in->ranges[0];
+ double earliest_ts = MP_NOPTS_VALUE;
+ struct demux_stream *earliest_stream = NULL;
+
+ for (int n = 0; n < range->num_streams; n++) {
+ struct demux_queue *queue = range->streams[n];
+ struct demux_stream *ds = queue->ds;
+
+ if (queue->head && queue->head != ds->reader_head) {
+ struct demux_packet *dp = queue->head;
+ double ts = dp->kf_seek_pts;
+ // Note: in obscure cases, packets might have no timestamps set,
+ // in which case we still need to prune _something_.
+ bool prune_always =
+ !in->seekable_cache || ts == MP_NOPTS_VALUE || !dp->keyframe;
+ if (prune_always || !earliest_stream || ts < earliest_ts) {
+ earliest_ts = ts;
+ earliest_stream = ds;
+ if (prune_always)
+ break;
+ }
+ }
+ }
+
+ assert(earliest_stream); // incorrect accounting of buffered sizes?
+ struct demux_stream *ds = earliest_stream;
+ struct demux_queue *queue = range->streams[ds->index];
+
+ // Prune all packets until the next keyframe or reader_head. Keeping
+ // those packets would not help with seeking at all, so we strictly
+ // drop them.
+ // In addition, we need to find the new possibly min. seek target,
+ // which in the worst case could be inside the forward buffer. The fact
+ // that many keyframe ranges without keyframes exist (audio packets)
+ // makes this much harder.
+ if (in->seekable_cache && !queue->next_prune_target) {
+ // (Has to be _after_ queue->head to drop at least 1 packet.)
+ struct demux_packet *prev = queue->head;
+ if (queue->seek_start != MP_NOPTS_VALUE)
+ queue->last_pruned = queue->seek_start;
+ queue->seek_start = MP_NOPTS_VALUE;
+ queue->next_prune_target = queue->tail; // (prune all if none found)
+ while (prev->next) {
+ struct demux_packet *dp = prev->next;
+ // Note that the next back_pts might be above the lowest buffered
+ // packet, but it will still be only viable lowest seek target.
+ if (dp->keyframe && dp->kf_seek_pts != MP_NOPTS_VALUE) {
+ queue->seek_start = dp->kf_seek_pts;
+ queue->next_prune_target = prev;
+ break;
+ }
+ prev = prev->next;
+ }
+
+ update_seek_ranges(range);
+ }
+
+ bool done = false;
+ while (!done && queue->head && queue->head != ds->reader_head) {
+ done = queue->next_prune_target == queue->head;
+ remove_head_packet(queue);
+ }
+
+ if (range != in->current_range && range->seek_start == MP_NOPTS_VALUE)
+ free_empty_cached_ranges(in);
+ }
+}
+
static void execute_trackswitch(struct demux_internal *in)
{
in->tracks_switched = false;
@@ -723,37 +1434,45 @@ static void execute_seek(struct demux_internal *in)
pthread_mutex_lock(&in->lock);
}
+// Make demuxing progress. Return whether progress was made.
+static bool thread_work(struct demux_internal *in)
+{
+ if (in->run_fn) {
+ in->run_fn(in->run_fn_arg);
+ in->run_fn = NULL;
+ pthread_cond_signal(&in->wakeup);
+ return true;
+ }
+ if (in->tracks_switched) {
+ execute_trackswitch(in);
+ return true;
+ }
+ if (in->seeking) {
+ execute_seek(in);
+ return true;
+ }
+ if (!in->eof) {
+ if (read_packet(in))
+ return true; // read_packet unlocked, so recheck conditions
+ }
+ if (in->force_cache_update) {
+ pthread_mutex_unlock(&in->lock);
+ update_cache(in);
+ pthread_mutex_lock(&in->lock);
+ in->force_cache_update = false;
+ return true;
+ }
+ return false;
+}
+
static void *demux_thread(void *pctx)
{
struct demux_internal *in = pctx;
mpthread_set_name("demux");
pthread_mutex_lock(&in->lock);
while (!in->thread_terminate) {
- if (in->run_fn) {
- in->run_fn(in->run_fn_arg);
- in->run_fn = NULL;
- pthread_cond_signal(&in->wakeup);
- continue;
- }
- if (in->tracks_switched) {
- execute_trackswitch(in);
+ if (thread_work(in))
continue;
- }
- if (in->seeking) {
- execute_seek(in);
- continue;
- }
- if (!in->eof) {
- if (read_packet(in))
- continue; // read_packet unlocked, so recheck conditions
- }
- if (in->force_cache_update) {
- pthread_mutex_unlock(&in->lock);
- update_cache(in);
- pthread_mutex_lock(&in->lock);
- in->force_cache_update = false;
- continue;
- }
pthread_cond_signal(&in->wakeup);
pthread_cond_wait(&in->wakeup, &in->lock);
}
@@ -763,24 +1482,38 @@ static void *demux_thread(void *pctx)
static struct demux_packet *dequeue_packet(struct demux_stream *ds)
{
- if (ds->attached_picture) {
+ if (ds->sh->attached_picture) {
ds->eof = true;
if (ds->attached_picture_added)
return NULL;
ds->attached_picture_added = true;
- return demux_copy_packet(ds->attached_picture);
+ struct demux_packet *pkt = demux_copy_packet(ds->sh->attached_picture);
+ if (!pkt)
+ abort();
+ pkt->stream = ds->sh->index;
+ return pkt;
}
- if (!ds->head)
+ if (!ds->reader_head)
return NULL;
- struct demux_packet *pkt = ds->head;
- ds->head = pkt->next;
+ struct demux_packet *pkt = ds->reader_head;
+ ds->reader_head = pkt->next;
+
+ // Update cached packet queue state.
+ ds->fw_packs--;
+ size_t bytes = demux_packet_estimate_total_size(pkt);
+ ds->fw_bytes -= bytes;
+ ds->in->fw_bytes -= bytes;
+
+ ds->last_ret_pos = pkt->pos;
+ ds->last_ret_dts = pkt->dts;
+
+ // The returned packet is mutated etc. and will be owned by the user.
+ pkt = demux_copy_packet(pkt);
+ if (!pkt)
+ abort();
pkt->next = NULL;
- if (!ds->head)
- ds->tail = NULL;
- ds->bytes -= demux_packet_estimate_total_size(pkt);
- ds->packs--;
- double ts = pkt->dts == MP_NOPTS_VALUE ? pkt->pts : pkt->dts;
+ double ts = PTS_OR_DEF(pkt->dts, pkt->pts);
if (ts != MP_NOPTS_VALUE)
ds->base_ts = ts;
@@ -807,66 +1540,46 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
pkt->pts = MP_ADD_PTS(pkt->pts, ds->in->ts_offset);
pkt->dts = MP_ADD_PTS(pkt->dts, ds->in->ts_offset);
- pkt->start = MP_ADD_PTS(pkt->start, ds->in->ts_offset);
- pkt->end = MP_ADD_PTS(pkt->end, ds->in->ts_offset);
+ if (pkt->segmented) {
+ pkt->start = MP_ADD_PTS(pkt->start, ds->in->ts_offset);
+ pkt->end = MP_ADD_PTS(pkt->end, ds->in->ts_offset);
+ }
+ prune_old_packets(ds->in);
return pkt;
}
-// Whether to avoid actively demuxing new packets to find a new packet on the
-// given stream.
-// Attached pictures (cover art) should never actively read.
-// Sparse packets (Subtitles) interleaved with other non-sparse packets (video,
-// audio) should never be read actively, meaning the demuxer thread does not
-// try to exceed default readahead in order to find a new packet.
-static bool use_lazy_packet_reading(struct demux_stream *ds)
-{
- if (ds->attached_picture)
- return true;
- if (ds->type != STREAM_SUB)
- return false;
- // Subtitles are only lazily read if there's at least 1 other actively read
- // stream.
- for (int n = 0; n < ds->in->num_streams; n++) {
- struct demux_stream *s = ds->in->streams[n]->ds;
- if (s->type != STREAM_SUB && s->selected && !s->eof && !s->attached_picture)
- return true;
- }
- return false;
-}
-
// Read a packet from the given stream. The returned packet belongs to the
// caller, who has to free it with talloc_free(). Might block. Returns NULL
// on EOF.
struct demux_packet *demux_read_packet(struct sh_stream *sh)
{
struct demux_stream *ds = sh ? sh->ds : NULL;
- struct demux_packet *pkt = NULL;
- if (ds) {
- struct demux_internal *in = ds->in;
- pthread_mutex_lock(&in->lock);
- if (!use_lazy_packet_reading(ds)) {
- const char *t = stream_type_name(ds->type);
- MP_DBG(in, "reading packet for %s\n", t);
- in->eof = false; // force retry
- while (ds->selected && !ds->head) {
- ds->active = true;
- // Note: the following code marks EOF if it can't continue
- if (in->threading) {
- MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
- pthread_cond_signal(&in->wakeup);
- pthread_cond_wait(&in->wakeup, &in->lock);
- } else {
- read_packet(in);
- }
- if (ds->eof)
- break;
+ if (!ds)
+ return NULL;
+ struct demux_internal *in = ds->in;
+ pthread_mutex_lock(&in->lock);
+ if (ds->eager) {
+ const char *t = stream_type_name(ds->type);
+ MP_DBG(in, "reading packet for %s\n", t);
+ in->eof = false; // force retry
+ while (ds->selected && !ds->reader_head) {
+ in->reading = true;
+ // Note: the following code marks EOF if it can't continue
+ if (in->threading) {
+ MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
+ pthread_cond_signal(&in->wakeup);
+ pthread_cond_wait(&in->wakeup, &in->lock);
+ } else {
+ thread_work(in);
}
+ if (ds->eof)
+ break;
}
- pkt = dequeue_packet(ds);
- pthread_cond_signal(&in->wakeup); // possibly read more
- pthread_mutex_unlock(&in->lock);
}
+ struct demux_packet *pkt = dequeue_packet(ds);
+ pthread_cond_signal(&in->wakeup); // possibly read more
+ pthread_mutex_unlock(&in->lock);
return pkt;
}
@@ -887,23 +1600,23 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
struct demux_stream *ds = sh ? sh->ds : NULL;
int r = -1;
*out_pkt = NULL;
- if (ds) {
- if (ds->in->threading) {
- pthread_mutex_lock(&ds->in->lock);
- *out_pkt = dequeue_packet(ds);
- if (use_lazy_packet_reading(ds)) {
- r = *out_pkt ? 1 : -1;
- } else {
- r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
- ds->active = ds->selected; // enable readahead
- ds->in->eof = false; // force retry
- pthread_cond_signal(&ds->in->wakeup); // possibly read more
- }
- pthread_mutex_unlock(&ds->in->lock);
+ if (!ds)
+ return r;
+ if (ds->in->threading) {
+ pthread_mutex_lock(&ds->in->lock);
+ *out_pkt = dequeue_packet(ds);
+ if (ds->eager) {
+ r = *out_pkt ? 1 : (ds->eof ? -1 : 0);
+ ds->in->reading = true; // enable readahead
+ ds->in->eof = false; // force retry
+ pthread_cond_signal(&ds->in->wakeup); // possibly read more
} else {
- *out_pkt = demux_read_packet(sh);
r = *out_pkt ? 1 : -1;
}
+ pthread_mutex_unlock(&ds->in->lock);
+ } else {
+ *out_pkt = demux_read_packet(sh);
+ r = *out_pkt ? 1 : -1;
}
return r;
}
@@ -914,7 +1627,7 @@ bool demux_has_packet(struct sh_stream *sh)
bool has_packet = false;
if (sh) {
pthread_mutex_lock(&sh->ds->in->lock);
- has_packet = sh->ds->head;
+ has_packet = sh->ds->reader_head;
pthread_mutex_unlock(&sh->ds->in->lock);
}
return has_packet;
@@ -928,23 +1641,20 @@ struct demux_packet *demux_read_any_packet(struct demuxer *demuxer)
bool read_more = true;
while (read_more) {
for (int n = 0; n < in->num_streams; n++) {
- struct sh_stream *sh = in->streams[n];
- sh->ds->active = sh->ds->selected; // force read_packet() to read
- struct demux_packet *pkt = dequeue_packet(sh->ds);
+ in->reading = true; // force read_packet() to read
+ struct demux_packet *pkt = dequeue_packet(in->streams[n]->ds);
if (pkt)
return pkt;
}
// retry after calling this
- pthread_mutex_lock(&in->lock); // lock only because read_packet unlocks
- read_more = read_packet(in);
+ pthread_mutex_lock(&in->lock); // lock only because thread_work unlocks
+ read_more = thread_work(in);
read_more &= !in->eof;
pthread_mutex_unlock(&in->lock);
}
return NULL;
}
-// ====================================================================
-
void demuxer_help(struct mp_log *log)
{
int i;
@@ -1107,6 +1817,9 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
}
}
+ if (src->events & DEMUX_EVENT_DURATION)
+ dst->duration = src->duration;
+
dst->events |= src->events;
src->events = 0;
}
@@ -1223,6 +1936,20 @@ static void demux_maybe_replace_stream(struct demuxer *demuxer)
}
}
+static void demux_init_ccs(struct demuxer *demuxer, struct demux_opts *opts)
+{
+ struct demux_internal *in = demuxer->in;
+ if (!opts->create_ccs)
+ return;
+ pthread_mutex_lock(&in->lock);
+ for (int n = 0; n < in->num_streams; n++) {
+ struct sh_stream *sh = in->streams[n];
+ if (sh->type == STREAM_VIDEO)
+ demuxer_get_cc_track_locked(sh);
+ }
+ pthread_mutex_unlock(&in->lock);
+}
+
static struct demuxer *open_given_type(struct mpv_global *global,
struct mp_log *log,
const struct demuxer_desc *desc,
@@ -1247,6 +1974,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
.is_network = stream->is_network,
.access_references = opts->access_references,
.events = DEMUX_EVENT_ALL,
+ .duration = -1,
};
demuxer->seekable = stream->seekable;
if (demuxer->stream->underlying && !demuxer->stream->underlying->seekable)
@@ -1259,13 +1987,21 @@ static struct demuxer *open_given_type(struct mpv_global *global,
.d_buffer = talloc(demuxer, struct demuxer),
.d_user = demuxer,
.min_secs = opts->min_secs,
- .max_packs = opts->max_packs,
.max_bytes = opts->max_bytes,
+ .max_bytes_bw = opts->max_bytes_bw,
.initial_state = true,
+ .highest_av_pts = MP_NOPTS_VALUE,
};
pthread_mutex_init(&in->lock, NULL);
pthread_cond_init(&in->wakeup, NULL);
+ in->current_range = talloc_ptrtype(in, in->current_range);
+ *in->current_range = (struct demux_cached_range){
+ .seek_start = MP_NOPTS_VALUE,
+ .seek_end = MP_NOPTS_VALUE,
+ };
+ MP_TARRAY_APPEND(in, in->ranges, in->num_ranges, in->current_range);
+
*in->d_thread = *demuxer;
*in->d_buffer = *demuxer;
@@ -1302,10 +2038,18 @@ static struct demuxer *open_given_type(struct mpv_global *global,
}
demux_init_cuesheet(in->d_thread);
demux_init_cache(demuxer);
+ demux_init_ccs(demuxer, opts);
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
demux_update(demuxer);
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD,
&(int){params ? params->initial_readahead : false});
+ int seekable = opts->seekable_cache;
+ if (demuxer->is_network || stream->caching) {
+ in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
+ if (seekable < 0)
+ seekable = 1;
+ }
+ in->seekable_cache = seekable == 1;
if (!(params && params->disable_timeline)) {
struct timeline *tl = timeline_load(global, log, demuxer);
if (tl) {
@@ -1321,8 +2065,6 @@ static struct demuxer *open_given_type(struct mpv_global *global,
}
}
}
- if (demuxer->is_network || stream->caching)
- in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
return demuxer;
}
@@ -1413,60 +2155,302 @@ struct demuxer *demux_open_url(const char *url,
return d;
}
-static void flush_locked(demuxer_t *demuxer)
+// called locked, from user thread only
+static void clear_reader_state(struct demux_internal *in)
{
- for (int n = 0; n < demuxer->in->num_streams; n++)
- ds_flush(demuxer->in->streams[n]->ds);
- demuxer->in->warned_queue_overflow = false;
- demuxer->in->eof = false;
- demuxer->in->last_eof = false;
- demuxer->in->idle = true;
- demuxer->filepos = -1; // implicitly synchronized
+ for (int n = 0; n < in->num_streams; n++)
+ ds_clear_reader_state(in->streams[n]->ds);
+ in->warned_queue_overflow = false;
+ in->d_user->filepos = -1; // implicitly synchronized
+ assert(in->fw_bytes == 0);
}
// clear the packet queues
void demux_flush(demuxer_t *demuxer)
{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
pthread_mutex_lock(&demuxer->in->lock);
- flush_locked(demuxer);
+ clear_reader_state(in);
+ for (int n = 0; n < in->num_ranges; n++)
+ clear_cached_range(in, in->ranges[n]);
+ free_empty_cached_ranges(in);
pthread_mutex_unlock(&demuxer->in->lock);
}
-int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
+// Does some (but not all) things for switching to another range.
+static void switch_current_range(struct demux_internal *in,
+ struct demux_cached_range *range)
{
- struct demux_internal *in = demuxer->in;
- assert(demuxer == in->d_user);
+ struct demux_cached_range *old = in->current_range;
+ assert(old != range);
- if (!demuxer->seekable) {
- MP_WARN(demuxer, "Cannot seek in this file.\n");
- return 0;
+ set_current_range(in, range);
+
+ // Remove packets which can't be used when seeking back to the range.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_queue *queue = old->streams[n];
+
+ // Remove all packets from head up until including next_prune_target.
+ while (queue->next_prune_target)
+ remove_head_packet(queue);
}
- if (seek_pts == MP_NOPTS_VALUE)
- return 0;
+ // Exclude weird corner cases that break resuming.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ // This is needed to resume or join the range at all.
+ if (ds->selected && !(ds->global_correct_dts || ds->global_correct_pos)) {
+ MP_VERBOSE(in, "discarding old range, due to stream %d: "
+ "correct_dts=%d correct_pos=%d\n", n,
+ ds->global_correct_dts, ds->global_correct_pos);
+ clear_cached_range(in, old);
+ break;
+ }
+ }
+
+ // Set up reading from new range (as well as writing to it).
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ ds->queue = range->streams[n];
+ ds->refreshing = false;
+ ds->eof = false;
+ }
+
+ // No point in keeping any junk (especially if old current_range is empty).
+ free_empty_cached_ranges(in);
+}
+
+static struct demux_packet *find_seek_target(struct demux_queue *queue,
+ double pts, int flags)
+{
+ struct demux_packet *start = queue->head;
+ for (int n = 0; n < queue->num_index; n++) {
+ if (queue->index[n]->kf_seek_pts > pts)
+ break;
+ start = queue->index[n];
+ }
+
+ struct demux_packet *target = NULL;
+ double target_diff = MP_NOPTS_VALUE;
+ for (struct demux_packet *dp = start; dp; dp = dp->next) {
+ double range_pts = dp->kf_seek_pts;
+ if (!dp->keyframe || range_pts == MP_NOPTS_VALUE)
+ continue;
+
+ double diff = range_pts - pts;
+ if (flags & SEEK_FORWARD) {
+ diff = -diff;
+ if (diff > 0)
+ continue;
+ }
+ if (target) {
+ if (diff <= 0) {
+ if (target_diff <= 0 && diff <= target_diff)
+ continue;
+ } else if (diff >= target_diff)
+ continue;
+ }
+ target_diff = diff;
+ target = dp;
+ if (range_pts > pts)
+ break;
+ }
+
+ return target;
+}
+
+// must be called locked
+static struct demux_cached_range *find_cache_seek_target(struct demux_internal *in,
+ double pts, int flags)
+{
+ // Note about queued low level seeks: in->seeking can be true here, and it
+ // might come from a previous resume seek to the current range. If we end
+ // up seeking into the current range (i.e. just changing time offset), the
+ // seek needs to continue. Otherwise, we override the queued seek anyway.
+ if ((flags & SEEK_FACTOR) || !in->seekable_cache)
+ return NULL;
+
+ for (int n = 0; n < in->num_ranges; n++) {
+ struct demux_cached_range *r = in->ranges[n];
+ if (r->seek_start != MP_NOPTS_VALUE) {
+ MP_VERBOSE(in, "cached range %d: %f <-> %f\n",
+ n, r->seek_start, r->seek_end);
+
+ if (pts >= r->seek_start && pts <= r->seek_end) {
+ MP_VERBOSE(in, "...using this range for in-cache seek.\n");
+ return r;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// must be called locked
+// range must be non-NULL and from find_cache_seek_target() using the same pts
+// and flags, before any other changes to the cached state
+static void execute_cache_seek(struct demux_internal *in,
+ struct demux_cached_range *range,
+ double pts, int flags)
+{
+ // Adjust the seek target to the found video key frames. Otherwise the
+ // video will undershoot the seek target, while audio will be closer to it.
+ // The player frontend will play the additional video without audio, so
+ // you get silent audio for the amount of "undershoot". Adjusting the seek
+ // target will make the audio seek to the video target or before.
+ // (If hr-seeks are used, it's better to skip this, as it would only mean
+ // that more audio data than necessary would have to be decoded.)
+ if (!(flags & SEEK_HR)) {
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ struct demux_queue *queue = range->streams[n];
+ if (ds->selected && ds->type == STREAM_VIDEO) {
+ struct demux_packet *target = find_seek_target(queue, pts, flags);
+ if (target) {
+ double target_pts = target->kf_seek_pts;
+ if (target_pts != MP_NOPTS_VALUE) {
+ MP_VERBOSE(in, "adjust seek target %f -> %f\n",
+ pts, target_pts);
+ // (We assume the find_seek_target() will return the
+ // same target for the video stream.)
+ pts = target_pts;
+ flags &= ~SEEK_FORWARD;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+ struct demux_queue *queue = range->streams[n];
+
+ struct demux_packet *target = find_seek_target(queue, pts, flags);
+ ds->reader_head = target;
+ ds->skip_to_keyframe = !target;
+ if (ds->reader_head)
+ ds->base_ts = PTS_OR_DEF(ds->reader_head->pts, ds->reader_head->dts);
+
+ recompute_buffers(ds);
+ in->fw_bytes += ds->fw_bytes;
+
+ MP_VERBOSE(in, "seeking stream %d (%s) to ",
+ n, stream_type_name(ds->type));
+
+ if (target) {
+ MP_VERBOSE(in, "packet %f/%f\n", target->pts, target->dts);
+ } else {
+ MP_VERBOSE(in, "nothing\n");
+ }
+ }
+
+ // If we seek to another range, we want to seek the low level demuxer to
+ // there as well, because reader and demuxer queue must be the same.
+ if (in->current_range != range) {
+ switch_current_range(in, range);
+
+ in->seeking = true;
+ in->seek_flags = SEEK_HR;
+ in->seek_pts = range->seek_end - 1.0;
+
+ // When new packets are being appended, they could overlap with the old
+ // range due to demuxer seek imprecisions, or because the queue contains
+ // packets past the seek target but before the next seek target. Don't
+ // append them twice, instead skip them until new packets are found.
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ ds->refreshing = ds->selected;
+ }
+
+ MP_VERBOSE(in, "resuming demuxer to end of cached range\n");
+ }
+}
+
+// Create a new blank ache range, and backup the old one. If the seekable
+// demuxer cache is disabled, merely reset the current range to a blank state.
+static void switch_to_fresh_cache_range(struct demux_internal *in)
+{
+ if (!in->seekable_cache) {
+ clear_cached_range(in, in->current_range);
+ return;
+ }
+
+ struct demux_cached_range *range = talloc_ptrtype(in, range);
+ *range = (struct demux_cached_range){
+ .seek_start = MP_NOPTS_VALUE,
+ .seek_end = MP_NOPTS_VALUE,
+ };
+ MP_TARRAY_APPEND(in, in->ranges, in->num_ranges, range);
+ add_missing_streams(in, range);
+
+ switch_current_range(in, range);
+}
- if (!(flags & SEEK_FORWARD))
- flags |= SEEK_BACKWARD;
+int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+ int res = 0;
pthread_mutex_lock(&in->lock);
+ if (seek_pts == MP_NOPTS_VALUE)
+ goto done;
+
MP_VERBOSE(in, "queuing seek to %f%s\n", seek_pts,
in->seeking ? " (cascade)" : "");
- flush_locked(demuxer);
- in->seeking = true;
- in->seek_flags = flags;
- in->seek_pts = seek_pts;
if (!(flags & SEEK_FACTOR))
- in->seek_pts = MP_ADD_PTS(in->seek_pts, -in->ts_offset);
+ seek_pts = MP_ADD_PTS(seek_pts, -in->ts_offset);
- if (!in->threading)
+ bool require_cache = flags & SEEK_CACHED;
+ flags &= ~(unsigned)SEEK_CACHED;
+
+ struct demux_cached_range *cache_target =
+ find_cache_seek_target(in, seek_pts, flags);
+
+ if (!cache_target) {
+ if (require_cache) {
+ MP_VERBOSE(demuxer, "Cached seek not possible.\n");
+ goto done;
+ }
+ if (!demuxer->seekable) {
+ MP_WARN(demuxer, "Cannot seek in this file.\n");
+ goto done;
+ }
+ }
+
+ clear_reader_state(in);
+
+ in->eof = false;
+ in->last_eof = false;
+ in->idle = true;
+ in->reading = false;
+
+ if (cache_target) {
+ execute_cache_seek(in, cache_target, seek_pts, flags);
+ } else {
+ switch_to_fresh_cache_range(in);
+
+ in->seeking = true;
+ in->seek_flags = flags;
+ in->seek_pts = seek_pts;
+ }
+
+ if (!in->threading && in->seeking)
execute_seek(in);
+ res = 1;
+
+done:
pthread_cond_signal(&in->wakeup);
pthread_mutex_unlock(&in->lock);
-
- return 1;
+ return res;
}
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
@@ -1481,6 +2465,83 @@ struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
return NULL;
}
+// An obscure mechanism to get stream switching to be executed "faster" (as
+// perceived by the user), by making the stream return packets from the
+// current position
+// On a switch, it seeks back, and then grabs all packets that were
+// "missing" from the packet queue of the newly selected stream.
+static void initiate_refresh_seek(struct demux_internal *in,
+ struct demux_stream *stream,
+ double start_ts)
+{
+ struct demuxer *demux = in->d_thread;
+ bool seekable = demux->desc->seek && demux->seekable &&
+ !demux->partially_seekable;
+
+ bool normal_seek = true;
+ bool refresh_possible = true;
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ if (!ds->selected)
+ continue;
+
+ if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
+ start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
+
+ // If there were no other streams selected, we can use a normal seek.
+ normal_seek &= stream == ds;
+
+ refresh_possible &= ds->queue->correct_dts || ds->queue->correct_pos;
+ }
+
+ if (start_ts == MP_NOPTS_VALUE || !seekable)
+ return;
+
+ if (!normal_seek) {
+ if (!refresh_possible) {
+ MP_VERBOSE(in, "can't issue refresh seek\n");
+ return;
+ }
+
+ for (int n = 0; n < in->num_streams; n++) {
+ struct demux_stream *ds = in->streams[n]->ds;
+
+ bool correct_pos = ds->queue->correct_pos;
+ bool correct_dts = ds->queue->correct_dts;
+
+ // We need to re-read all packets anyway, so discard the buffered
+ // data. (In theory, we could keep the packets, and be able to use
+ // it for seeking if partially read streams are deselected again,
+ // but this causes other problems like queue overflows when
+ // selecting a new stream.)
+ ds_clear_reader_queue_state(ds);
+ clear_queue(ds->queue);
+
+ // Streams which didn't have any packets yet will return all packets,
+ // other streams return packets only starting from the last position.
+ if (ds->selected && (ds->last_ret_pos != -1 ||
+ ds->last_ret_dts != MP_NOPTS_VALUE))
+ {
+ ds->refreshing = true;
+ ds->queue->correct_dts = correct_dts;
+ ds->queue->correct_pos = correct_pos;
+ ds->queue->last_pos = ds->last_ret_pos;
+ ds->queue->last_dts = ds->last_ret_dts;
+ }
+
+ update_seek_ranges(in->current_range);
+ }
+
+ start_ts -= 1.0; // small offset to get correct overlap
+ }
+
+ MP_VERBOSE(in, "refresh seek to %f\n", start_ts);
+ in->seeking = true;
+ in->seek_flags = SEEK_HR;
+ in->seek_pts = start_ts;
+}
+
// Set whether the given stream should return packets.
// ref_pts is used only if the stream is enabled. Then it serves as approximate
// start pts for this stream (in the worst case it is ignored).
@@ -1488,15 +2549,16 @@ void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
double ref_pts, bool selected)
{
struct demux_internal *in = demuxer->in;
+ struct demux_stream *ds = stream->ds;
pthread_mutex_lock(&in->lock);
// don't flush buffers if stream is already selected / unselected
- if (stream->ds->selected != selected) {
- stream->ds->selected = selected;
- ds_flush(stream->ds);
+ if (ds->selected != selected) {
+ MP_VERBOSE(in, "%sselect track %d\n", selected ? "" : "de", stream->index);
+ ds->selected = selected;
+ update_stream_selection_state(in, ds);
in->tracks_switched = true;
- stream->ds->need_refresh = selected && !in->initial_state;
- if (stream->ds->need_refresh)
- in->ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
+ if (ds->selected && !in->initial_state)
+ initiate_refresh_seek(in, ds, MP_ADD_PTS(ref_pts, -in->ts_offset));
if (in->threading) {
pthread_cond_signal(&in->wakeup);
} else {
@@ -1512,6 +2574,10 @@ void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect)
demuxer->in->autoselect = autoselect;
}
+// This is for demuxer implementations only. demuxer_select_track() sets the
+// logical state, while this function returns the actual state (in case the
+// demuxer attempts to cache even unselected packets for track switching - this
+// will potentially be done in the future).
bool demux_stream_is_selected(struct sh_stream *stream)
{
if (!stream)
@@ -1572,6 +2638,24 @@ int demuxer_add_chapter(demuxer_t *demuxer, char *name,
return demuxer->num_chapters - 1;
}
+void demux_disable_cache(demuxer_t *demuxer)
+{
+ struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
+
+ pthread_mutex_lock(&in->lock);
+ if (in->seekable_cache) {
+ MP_VERBOSE(demuxer, "disabling persistent packet cache\n");
+ in->seekable_cache = false;
+
+ // Get rid of potential buffered ranges floating around.
+ free_empty_cached_ranges(in);
+ // Get rid of potential old packets in the current range.
+ prune_old_packets(in);
+ }
+ pthread_mutex_unlock(&in->lock);
+}
+
// must be called not locked
static void update_cache(struct demux_internal *in)
{
@@ -1653,27 +2737,41 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
struct demux_ctrl_reader_state *r = arg;
*r = (struct demux_ctrl_reader_state){
.eof = in->last_eof,
- .ts_range = {MP_NOPTS_VALUE, MP_NOPTS_VALUE},
+ .ts_reader = MP_NOPTS_VALUE,
+ .ts_end = MP_NOPTS_VALUE,
.ts_duration = -1,
+ .total_bytes = in->total_bytes,
+ .fw_bytes = in->fw_bytes,
};
- int num_packets = 0;
+ bool any_packets = false;
for (int n = 0; n < in->num_streams; n++) {
struct demux_stream *ds = in->streams[n]->ds;
- if (ds->active && !(!ds->head && ds->eof)) {
- r->underrun |= !ds->head && !ds->eof;
- r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts);
- r->ts_range[1] = MP_PTS_MIN(r->ts_range[1], ds->last_ts);
- num_packets += ds->packs;
+ if (ds->eager && !(!ds->queue->head && ds->eof) && !ds->ignore_eof)
+ {
+ r->underrun |= !ds->reader_head && !ds->eof;
+ r->ts_reader = MP_PTS_MAX(r->ts_reader, ds->base_ts);
+ r->ts_end = MP_PTS_MAX(r->ts_end, ds->queue->last_ts);
+ any_packets |= !!ds->queue->head;
}
}
r->idle = (in->idle && !r->underrun) || r->eof;
r->underrun &= !r->idle;
- if (r->ts_range[0] != MP_NOPTS_VALUE && r->ts_range[1] != MP_NOPTS_VALUE)
- r->ts_duration = MPMAX(0, r->ts_range[1] - r->ts_range[0]);
- if (!num_packets || in->seeking)
+ r->ts_reader = MP_ADD_PTS(r->ts_reader, in->ts_offset);
+ r->ts_end = MP_ADD_PTS(r->ts_end, in->ts_offset);
+ if (r->ts_reader != MP_NOPTS_VALUE && r->ts_reader <= r->ts_end)
+ r->ts_duration = r->ts_end - r->ts_reader;
+ if (in->seeking || !any_packets)
r->ts_duration = 0;
- r->ts_range[0] = MP_ADD_PTS(r->ts_range[0], in->ts_offset);
- r->ts_range[1] = MP_ADD_PTS(r->ts_range[1], in->ts_offset);
+ for (int n = 0; n < MPMIN(in->num_ranges, MAX_SEEK_RANGES); n++) {
+ struct demux_cached_range *range = in->ranges[n];
+ if (range->seek_start != MP_NOPTS_VALUE) {
+ r->seek_ranges[r->num_seek_ranges++] =
+ (struct demux_seek_range){
+ .start = MP_ADD_PTS(range->seek_start, in->ts_offset),
+ .end = MP_ADD_PTS(range->seek_end, in->ts_offset),
+ };
+ }
+ }
return CONTROL_OK;
}
}
diff --git a/demux/demux.h b/demux/demux.h
index 4fac200..85bd5fd 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -40,10 +40,23 @@ enum demux_ctrl {
DEMUXER_CTRL_REPLACE_STREAM,
};
+#define MAX_SEEK_RANGES 10
+
+struct demux_seek_range {
+ double start, end;
+};
+
struct demux_ctrl_reader_state {
bool eof, underrun, idle;
- double ts_range[2]; // start, end
double ts_duration;
+ double ts_reader; // approx. timerstamp of decoder position
+ double ts_end; // approx. timestamp of end of buffered range
+ int64_t total_bytes;
+ int64_t fw_bytes;
+ // Positions that can be seeked to without incurring the latency of a low
+ // level seek.
+ int num_seek_ranges;
+ struct demux_seek_range seek_ranges[MAX_SEEK_RANGES];
};
struct demux_ctrl_stream_ctrl {
@@ -54,7 +67,8 @@ struct demux_ctrl_stream_ctrl {
#define SEEK_FACTOR (1 << 1) // argument is in range [0,1]
#define SEEK_FORWARD (1 << 2) // prefer later time if not exact
-#define SEEK_BACKWARD (1 << 3) // prefer earlier time if not exact
+ // (if unset, prefer earlier time)
+#define SEEK_CACHED (1 << 3) // allow packet cache seeks only
#define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only)
// Strictness of the demuxer open format check.
@@ -77,6 +91,7 @@ enum demux_event {
DEMUX_EVENT_INIT = 1 << 0, // complete (re-)initialization
DEMUX_EVENT_STREAMS = 1 << 1, // a stream was added
DEMUX_EVENT_METADATA = 1 << 2, // metadata or stream_metadata changed
+ DEMUX_EVENT_DURATION = 1 << 3, // duration updated
DEMUX_EVENT_ALL = 0xFFFF,
};
@@ -285,6 +300,8 @@ int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
void demux_changed(demuxer_t *demuxer, int events);
void demux_update(demuxer_t *demuxer);
+void demux_disable_cache(demuxer_t *demuxer);
+
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
enum stream_type t, int id);
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index e61529f..393ed92 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -3,20 +3,18 @@
*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
@@ -35,13 +33,10 @@
#include <libavutil/avstring.h>
#include <libavutil/mathematics.h>
#include <libavutil/replaygain.h>
+#include <libavutil/spherical.h>
#include <libavutil/display.h>
#include <libavutil/opt.h>
-#if HAVE_AVUTIL_SPHERICAL
-#include <libavutil/spherical.h>
-#endif
-
#include "common/msg.h"
#include "common/tags.h"
#include "common/av_common.h"
@@ -244,7 +239,7 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
ret, stream, buf, size, stream_tell(stream), stream->eof);
- return ret;
+ return ret ? ret : AVERROR_EOF;
}
static int64_t mp_seek(void *opaque, int64_t pos, int whence)
@@ -408,7 +403,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
AVProbeData avpd = {
// Disable file-extension matching with normal checks
- .filename = check <= DEMUX_CHECK_REQUEST ? priv->filename : "",
+ .filename = priv->filename,
.buf_size = 0,
.buf = av_mallocz(PROBE_BUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE),
};
@@ -645,7 +640,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->codec->rotate = (((int)(-r) % 360) + 360) % 360;
}
-#if HAVE_AVUTIL_SPHERICAL
sd = av_stream_get_side_data(st, AV_PKT_DATA_SPHERICAL, NULL);
if (sd) {
AVSphericalMapping *sp = (void *)sd;
@@ -656,7 +650,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
mpsp->ref_angles[1] = sp->pitch / (float)(1 << 16);
mpsp->ref_angles[2] = sp->roll / (float)(1 << 16);
}
-#endif
// This also applies to vfw-muxed mkv, but we can't detect these easily.
sh->codec->avi_dts = matches_avinputformat_name(priv, "avi");
@@ -812,13 +805,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
if (index_mode != 1)
avfc->flags |= AVFMT_FLAG_IGNIDX;
-#if LIBAVFORMAT_VERSION_MICRO >= 100
- /* Keep side data as side data instead of mashing it into the packet
- * stream.
- * Note: Libav doesn't have this horrible insanity. */
- av_opt_set(avfc, "fflags", "+keepside", 0);
-#endif
-
if (lavfdopts->probesize) {
if (av_opt_set_int(avfc, "probesize", lavfdopts->probesize, 0) < 0)
MP_ERR(demuxer, "couldn't set option probesize to %u\n",
@@ -1016,7 +1002,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
int avsflags = 0;
int64_t seek_pts_av = 0;
- if (flags & SEEK_BACKWARD)
+ if (!(flags & SEEK_FORWARD))
avsflags = AVSEEK_FLAG_BACKWARD;
if (flags & SEEK_FACTOR) {
@@ -1033,7 +1019,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
seek_pts_av = seek_pts * priv->avfc->duration;
}
} else {
- if (flags & SEEK_BACKWARD)
+ if (!(flags & SEEK_FORWARD))
seek_pts -= priv->seek_delay;
seek_pts_av = seek_pts * AV_TIME_BASE;
}
diff --git a/demux/demux_mf.c b/demux/demux_mf.c
index 04d37f4..c4995a6 100644
--- a/demux/demux_mf.c
+++ b/demux/demux_mf.c
@@ -4,7 +4,7 @@
* mpv 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 3 of the License, or (at your option) any later version.
+ * version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -13,12 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * The parts making this file LGPL v3 (instead of v2.1 or later) are:
- * 0aa37a0db23c allow autodetection of pictures type when using mf://@fil...
- * (iive agreed to LGPL v3+ only.)
- * Once these changes are not relevant to for copyright anymore (e.g. because
- * they have been removed), this file will change to LGPLv2.1+.
*/
#include <stdio.h>
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index 4b9551a..f513496 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -18,8 +18,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#include <stdlib.h>
@@ -161,8 +159,9 @@ struct block_info {
bool simple, keyframe, duration_known;
int64_t timecode;
mkv_track_t *track;
- bstr data;
- void *alloc;
+ // Actual packet data.
+ AVBufferRef *laces[MAX_NUM_LACES];
+ int num_laces;
int64_t filepos;
struct ebml_block_additions *additions;
};
@@ -238,7 +237,6 @@ const struct m_sub_options demux_mkv_conf = {
.subtitle_preroll = 2,
.subtitle_preroll_secs = 1.0,
.subtitle_preroll_secs_index = 10.0,
- .probe_start_time = 1,
},
};
@@ -702,12 +700,7 @@ static void parse_trackentry(struct demuxer *demuxer,
}
track->uid = entry->track_uid;
- if (entry->name) {
- track->name = talloc_strdup(track, entry->name);
-#if HAVE_GPL
- MP_VERBOSE(demuxer, "| + Name: %s\n", track->name);
-#endif
- }
+ track->name = talloc_strdup(track, entry->name);
track->type = entry->track_type;
MP_VERBOSE(demuxer, "| + Track type: ");
@@ -1383,6 +1376,7 @@ static const char *const mkv_video_tags[][2] = {
{"V_DIRAC", "dirac"},
{"V_PRORES", "prores"},
{"V_MPEGH/ISO/HEVC", "hevc"},
+ {"V_SNOW", "snow"},
{0}
};
@@ -1468,12 +1462,8 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
}
const char *codec = sh_v->codec ? sh_v->codec : "";
- if (!strcmp(codec, "vp9")) {
- track->parse = true;
- track->parse_timebase = 1e9;
- } else if (!strcmp(codec, "mjpeg")) {
+ if (!strcmp(codec, "mjpeg"))
sh_v->codec_tag = MKTAG('m', 'j', 'p', 'g');
- }
if (extradata_size > 0x1000000) {
MP_WARN(demuxer, "Invalid CodecPrivate\n");
@@ -2088,86 +2078,91 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
return 0;
}
-static bool bstr_read_u8(bstr *buffer, uint8_t *out_u8)
+// Read the laced block data at the current stream position (until endpos as
+// indicated by the block length field) into individual buffers.
+static int demux_mkv_read_block_lacing(struct block_info *block, int type,
+ struct stream *s, uint64_t endpos)
{
- if (buffer->len > 0) {
- *out_u8 = buffer->start[0];
- buffer->len -= 1;
- buffer->start += 1;
- return true;
- } else {
- return false;
- }
-}
+ int laces;
+ uint32_t lace_size[MAX_NUM_LACES];
-static int demux_mkv_read_block_lacing(bstr *buffer, int *laces,
- uint32_t lace_size[MAX_NUM_LACES])
-{
- uint32_t total = 0;
- uint8_t flags, t;
- int i;
- /* lacing flags */
- if (!bstr_read_u8(buffer, &flags))
- goto error;
-
- int type = (flags >> 1) & 0x03;
if (type == 0) { /* no lacing */
- *laces = 1;
- lace_size[0] = buffer->len;
+ laces = 1;
+ lace_size[0] = endpos - stream_tell(s);
} else {
- if (!bstr_read_u8(buffer, &t))
+ laces = stream_read_char(s);
+ if (laces < 0 || stream_tell(s) > endpos)
goto error;
- *laces = t + 1;
+ laces += 1;
switch (type) {
- case 1: /* xiph lacing */
- for (i = 0; i < *laces - 1; i++) {
+ case 1: { /* xiph lacing */
+ uint32_t total = 0;
+ for (int i = 0; i < laces - 1; i++) {
lace_size[i] = 0;
+ uint8_t t;
do {
- if (!bstr_read_u8(buffer, &t))
+ t = stream_read_char(s);
+ if (stream_eof(s) || stream_tell(s) >= endpos)
goto error;
lace_size[i] += t;
} while (t == 0xFF);
total += lace_size[i];
}
- lace_size[i] = buffer->len - total;
+ uint32_t rest_length = endpos - stream_tell(s);
+ lace_size[laces - 1] = rest_length - total;
break;
+ }
- case 2: /* fixed-size lacing */
- for (i = 0; i < *laces; i++)
- lace_size[i] = buffer->len / *laces;
+ case 2: { /* fixed-size lacing */
+ uint32_t full_length = endpos - stream_tell(s);
+ for (int i = 0; i < laces; i++)
+ lace_size[i] = full_length / laces;
break;
+ }
- case 3:; /* EBML lacing */
- uint64_t num = ebml_read_vlen_uint(buffer);
- if (num == EBML_UINT_INVALID)
- goto error;
- if (num > buffer->len)
+ case 3: { /* EBML lacing */
+ uint64_t num = ebml_read_length(s);
+ if (num == EBML_UINT_INVALID || stream_tell(s) >= endpos)
goto error;
- total = lace_size[0] = num;
- for (i = 1; i < *laces - 1; i++) {
- int64_t snum = ebml_read_vlen_int(buffer);
- if (snum == EBML_INT_INVALID)
+ uint32_t total = lace_size[0] = num;
+ for (int i = 1; i < laces - 1; i++) {
+ int64_t snum = ebml_read_signed_length(s);
+ if (snum == EBML_INT_INVALID || stream_tell(s) >= endpos)
goto error;
lace_size[i] = lace_size[i - 1] + snum;
total += lace_size[i];
}
- lace_size[i] = buffer->len - total;
+ uint32_t rest_length = endpos - stream_tell(s);
+ lace_size[laces - 1] = rest_length - total;
break;
+ }
- default: abort();
+ default:
+ goto error;
}
}
- total = buffer->len;
- for (i = 0; i < *laces; i++) {
- if (lace_size[i] > total)
+ for (int i = 0; i < laces; i++) {
+ uint32_t size = lace_size[i];
+ if (stream_tell(s) + size > endpos || size > (1 << 30))
goto error;
- total -= lace_size[i];
+ int pad = MPMAX(AV_INPUT_BUFFER_PADDING_SIZE, AV_LZO_INPUT_PADDING);
+ AVBufferRef *buf = av_buffer_alloc(size + pad);
+ if (!buf)
+ goto error;
+ buf->size = size;
+ if (stream_read(s, buf->data, buf->size) != buf->size) {
+ av_buffer_unref(&buf);
+ goto error;
+ }
+ memset(buf->data + buf->size, 0, pad);
+ block->laces[block->num_laces++] = buf;
}
- if (total != 0)
+
+ if (stream_tell(s) != endpos)
goto error;
return 0;
@@ -2448,12 +2443,10 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
struct demux_packet *new = new_demux_packet_from(data, size);
if (!new)
break;
- demux_packet_copy_attribs(new, dp);
-#if LIBAVCODEC_VERSION_MICRO >= 100
if (copy_sidedata)
- av_copy_packet_side_data(new->avpacket, dp->avpacket);
-#endif
+ av_packet_copy_props(new->avpacket, dp->avpacket);
copy_sidedata = false;
+ demux_packet_copy_attribs(new, dp);
if (track->parse_timebase) {
new->pts = track->av_parser->pts == AV_NOPTS_VALUE
? MP_NOPTS_VALUE : track->av_parser->pts / tb;
@@ -2474,11 +2467,10 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
static void free_block(struct block_info *block)
{
- free(block->alloc);
- block->alloc = NULL;
- block->data = (bstr){0};
- talloc_free(block->additions);
- block->additions = NULL;
+ for (int n = 0; n < block->num_laces; n++)
+ av_buffer_unref(&block->laces[n]);
+ block->num_laces = 0;
+ TA_FREEP(&block->additions);
}
static void index_block(demuxer_t *demuxer, struct block_info *block)
@@ -2498,33 +2490,38 @@ static int read_block(demuxer_t *demuxer, int64_t end, struct block_info *block)
uint64_t num;
int16_t time;
uint64_t length;
- int res = -1;
free_block(block);
length = ebml_read_length(s);
- if (length > 500000000 || stream_tell(s) + length > (uint64_t)end)
- goto exit;
- block->alloc = malloc(length + AV_LZO_INPUT_PADDING);
- if (!block->alloc)
- goto exit;
- block->data = (bstr){block->alloc, length};
- block->filepos = stream_tell(s);
- if (stream_read(s, block->data.start, block->data.len) != block->data.len)
- goto exit;
+ if (!length || length > 500000000 || stream_tell(s) + length > (uint64_t)end)
+ return -1;
+
+ uint64_t endpos = stream_tell(s) + length;
+ int res = -1;
// Parse header of the Block element
/* first byte(s): track num */
- num = ebml_read_vlen_uint(&block->data);
- if (num == EBML_UINT_INVALID)
+ num = ebml_read_length(s);
+ if (num == EBML_UINT_INVALID || stream_tell(s) >= endpos)
goto exit;
+
/* time (relative to cluster time) */
- if (block->data.len < 3)
+ if (stream_tell(s) + 3 > endpos)
goto exit;
- time = block->data.start[0] << 8 | block->data.start[1];
- block->data.start += 2;
- block->data.len -= 2;
+ uint8_t c1 = stream_read_char(s);
+ uint8_t c2 = stream_read_char(s);
+ time = c1 << 8 | c2;
+
+ uint8_t header_flags = stream_read_char(s);
+
+ block->filepos = stream_tell(s);
+
+ int lace_type = (header_flags >> 1) & 0x03;
+ if (demux_mkv_read_block_lacing(block, lace_type, s, endpos))
+ goto exit;
+
if (block->simple)
- block->keyframe = block->data.start[0] & 0x80;
+ block->keyframe = header_flags & 0x80;
block->timecode = time * mkv_d->tc_scale + mkv_d->cluster_tc;
for (int i = 0; i < mkv_d->num_tracks; i++) {
if (mkv_d->tracks[i]->tnum == num) {
@@ -2537,35 +2534,31 @@ static int read_block(demuxer_t *demuxer, int64_t end, struct block_info *block)
goto exit;
}
+ if (stream_tell(s) != endpos)
+ goto exit;
+
res = 1;
exit:
if (res <= 0)
free_block(block);
+ stream_seek(s, endpos);
return res;
}
static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
{
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
- int laces;
double current_pts;
- bstr data = block_info->data;
bool keyframe = block_info->keyframe;
uint64_t block_duration = block_info->duration;
int64_t tc = block_info->timecode;
mkv_track_t *track = block_info->track;
struct sh_stream *stream = track->stream;
- uint32_t lace_size[MAX_NUM_LACES];
bool use_this_block = tc >= mkv_d->skip_to_timecode;
if (!demux_stream_is_selected(stream))
return 0;
- if (demux_mkv_read_block_lacing(&data, &laces, lace_size)) {
- MP_ERR(demuxer, "Bad input [lacing]\n");
- return 0;
- }
-
current_pts = tc / 1e9 - track->codec_delay;
if (track->require_keyframes && !keyframe) {
@@ -2597,7 +2590,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
}
}
if (use_this_block) {
- if (laces > 1) {
+ if (block_info->num_laces > 1) {
MP_WARN(demuxer, "Subtitles use Matroska "
"lacing. This is abnormal and not supported.\n");
use_this_block = 0;
@@ -2611,15 +2604,22 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
if (use_this_block) {
uint64_t filepos = block_info->filepos;
- for (int i = 0; i < laces; i++) {
- bstr block = bstr_splice(data, 0, lace_size[i]);
- data = bstr_cut(data, lace_size[i]);
+ for (int i = 0; i < block_info->num_laces; i++) {
+ AVBufferRef *data = block_info->laces[i];
+ demux_packet_t *dp = NULL;
- block = demux_mkv_decode(demuxer->log, track, block, 1);
+ bstr block = {data->data, data->size};
+ bstr nblock = demux_mkv_decode(demuxer->log, track, block, 1);
- demux_packet_t *dp = new_demux_packet_from(block.start, block.len);
+ if (block.start != nblock.start || block.len != nblock.len) {
+ // (avoidable copy of the entire data)
+ dp = new_demux_packet_from(nblock.start, nblock.len);
+ } else {
+ dp = new_demux_packet_from_buf(data);
+ }
if (!dp)
break;
+
dp->keyframe = keyframe;
dp->pos = filepos;
/* If default_duration is 0, assume no pts value is known
@@ -2651,7 +2651,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
mkv_parse_and_add_packet(demuxer, track, dp);
talloc_free_children(track->parser_tmp);
- filepos += block.len;
+ filepos += data->size;
}
if (stream->type == STREAM_VIDEO) {
@@ -2729,7 +2729,7 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
}
}
- return block->data.start ? 1 : 0;
+ return block->num_laces ? 1 : 0;
error:
free_block(block);
@@ -2741,7 +2741,7 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
stream_t *s = demuxer->stream;
- if (mkv_d->tmp_block.alloc) {
+ if (mkv_d->tmp_block.num_laces) {
*block = mkv_d->tmp_block;
mkv_d->tmp_block = (struct block_info){0};
return 1;
@@ -2902,8 +2902,6 @@ static int create_index_until(struct demuxer *demuxer, int64_t timecode)
return 0;
}
-#define FLAG_BACKWARD 1
-#define FLAG_SUBPREROLL 2
static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
int64_t target_timecode, int flags)
{
@@ -2914,8 +2912,8 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
if (seek_id < 0 || mkv_d->indexes[i].tnum == seek_id) {
int64_t diff =
- target_timecode - mkv_d->indexes[i].timecode * mkv_d->tc_scale;
- if (flags & FLAG_BACKWARD)
+ mkv_d->indexes[i].timecode * mkv_d->tc_scale - target_timecode;
+ if (flags & SEEK_FORWARD)
diff = -diff;
if (min_diff != INT64_MIN) {
if (diff <= 0) {
@@ -2931,7 +2929,7 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
if (index) { /* We've found an entry. */
uint64_t seek_pos = index->filepos;
- if (flags & FLAG_SUBPREROLL) {
+ if (flags & SEEK_HR) {
// Find the cluster with the highest filepos, that has a timestamp
// still lower than min_tc.
double secs = mkv_d->opts->subtitle_preroll_secs;
@@ -2996,14 +2994,12 @@ static void demux_mkv_seek(demuxer_t *demuxer, double seek_pts, int flags)
}
}
- int cueflags = (flags & SEEK_BACKWARD) ? FLAG_BACKWARD : 0;
-
mkv_d->subtitle_preroll = NUM_SUB_PREROLL_PACKETS;
int preroll_opt = mkv_d->opts->subtitle_preroll;
- if (((flags & SEEK_HR) || preroll_opt == 1 ||
- (preroll_opt == 2 && mkv_d->index_has_durations))
- && st_active[STREAM_SUB] && st_active[STREAM_VIDEO])
- cueflags |= FLAG_SUBPREROLL;
+ if (preroll_opt == 1 || (preroll_opt == 2 && mkv_d->index_has_durations))
+ flags |= SEEK_HR;
+ if (!st_active[STREAM_SUB])
+ flags &= ~SEEK_HR;
// Adjust the target a little bit to catch cases where the target position
// specifies a keyframe with high, but not perfect, precision.
@@ -3017,9 +3013,9 @@ static void demux_mkv_seek(demuxer_t *demuxer, double seek_pts, int flags)
if (create_index_until(demuxer, target_timecode) >= 0) {
int seek_id = st_active[STREAM_VIDEO] ? v_tnum : a_tnum;
- index = seek_with_cues(demuxer, seek_id, target_timecode, cueflags);
+ index = seek_with_cues(demuxer, seek_id, target_timecode, flags);
if (!index)
- index = seek_with_cues(demuxer, -1, target_timecode, cueflags);
+ index = seek_with_cues(demuxer, -1, target_timecode, flags);
}
if (!index)
diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c
index d79edfc..3a65ada 100644
--- a/demux/demux_playlist.c
+++ b/demux/demux_playlist.c
@@ -177,12 +177,13 @@ static int parse_ref_init(struct pl_parser *p)
return 0;
}
-static int parse_pls(struct pl_parser *p)
+static int parse_ini_thing(struct pl_parser *p, const char *header,
+ const char *entry)
{
bstr line = {0};
while (!line.len && !pl_eof(p))
line = bstr_strip(pl_get_line(p));
- if (bstrcasecmp0(line, "[playlist]") != 0)
+ if (bstrcasecmp0(line, header) != 0)
return -1;
if (p->probing)
return 0;
@@ -190,7 +191,7 @@ static int parse_pls(struct pl_parser *p)
line = bstr_strip(pl_get_line(p));
bstr key, value;
if (bstr_split_tok(line, "=", &key, &value) &&
- bstr_case_startswith(key, bstr0("File")))
+ bstr_case_startswith(key, bstr0(entry)))
{
value = bstr_strip(value);
if (bstr_startswith0(value, "\"") && bstr_endswith0(value, "\""))
@@ -201,6 +202,16 @@ static int parse_pls(struct pl_parser *p)
return 0;
}
+static int parse_pls(struct pl_parser *p)
+{
+ return parse_ini_thing(p, "[playlist]", "File");
+}
+
+static int parse_url(struct pl_parser *p)
+{
+ return parse_ini_thing(p, "[InternetShortcut]", "URL");
+}
+
static int parse_txt(struct pl_parser *p)
{
if (!p->force)
@@ -221,7 +232,7 @@ static int parse_txt(struct pl_parser *p)
static bool same_st(struct stat *st1, struct stat *st2)
{
- return HAVE_POSIX && st1->st_dev == st2->st_dev && st1->st_ino == st2->st_ino;
+ return st1->st_dev == st2->st_dev && st1->st_ino == st2->st_ino;
}
// Return true if this was a readable directory.
@@ -319,6 +330,7 @@ static const struct pl_format formats[] = {
{"ini", parse_ref_init},
{"pls", parse_pls,
MIME_TYPES("audio/x-scpls")},
+ {"url", parse_url},
{"txt", parse_txt},
};
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index d7a5c36..4b9b312 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -46,7 +46,6 @@ struct segment {
struct virtual_stream {
struct sh_stream *sh; // stream exported by demux_timeline
bool selected; // ==demux_stream_is_selected(sh)
- bool new_segment; // whether a new segment needs to be signaled
int eos_packets; // deal with b-frame delay
};
@@ -61,7 +60,7 @@ struct priv {
struct segment *current;
// As the demuxer user sees it.
- struct virtual_stream *streams;
+ struct virtual_stream **streams;
int num_streams;
// Total number of packets received past end of segment. Used
@@ -97,7 +96,7 @@ static void associate_streams(struct demuxer *demuxer, struct segment *seg)
if (!other || !target_stream_used(seg, other->index)) {
// Try to associate the first unused stream with matching media type.
for (int i = 0; i < p->num_streams; i++) {
- struct sh_stream *cur = p->streams[i].sh;
+ struct sh_stream *cur = p->streams[i]->sh;
if (cur->type == sh->type && !target_stream_used(seg, cur->index))
{
other = cur;
@@ -118,7 +117,7 @@ static void reselect_streams(struct demuxer *demuxer)
struct priv *p = demuxer->priv;
for (int n = 0; n < p->num_streams; n++) {
- struct virtual_stream *vs = &p->streams[n];
+ struct virtual_stream *vs = p->streams[n];
vs->selected = demux_stream_is_selected(vs->sh);
}
@@ -131,7 +130,7 @@ static void reselect_streams(struct demuxer *demuxer)
struct sh_stream *sh = demux_get_stream(seg->d, i);
bool selected = false;
if (seg->stream_map[i] >= 0)
- selected = p->streams[seg->stream_map[i]].selected;
+ selected = p->streams[seg->stream_map[i]]->selected;
// This stops demuxer readahead for inactive segments.
if (!p->current || seg->d != p->current->d)
selected = false;
@@ -171,6 +170,8 @@ static void reopen_lazy_segments(struct demuxer *demuxer)
demuxer->stream->cancel, demuxer->global);
if (!p->current->d && !demux_cancel_test(demuxer))
MP_ERR(demuxer, "failed to load segment\n");
+ if (p->current->d)
+ demux_disable_cache(p->current->d);
associate_streams(demuxer, p->current);
}
@@ -179,8 +180,8 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
{
struct priv *p = demuxer->priv;
- if (!(flags & (SEEK_FORWARD | SEEK_BACKWARD)))
- flags |= SEEK_BACKWARD | SEEK_HR;
+ if (!(flags & SEEK_FORWARD))
+ flags |= SEEK_HR;
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
@@ -195,8 +196,7 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
demux_seek(new->d, start_pts, flags);
for (int n = 0; n < p->num_streams; n++) {
- struct virtual_stream *vs = &p->streams[n];
- vs->new_segment = true;
+ struct virtual_stream *vs = p->streams[n];
vs->eos_packets = 0;
}
@@ -209,7 +209,7 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
double pts = seek_pts * ((flags & SEEK_FACTOR) ? p->duration : 1);
- flags &= SEEK_FORWARD | SEEK_BACKWARD | SEEK_HR;
+ flags &= SEEK_FORWARD | SEEK_HR;
struct segment *new = p->segments[p->num_segments - 1];
for (int n = 0; n < p->num_segments; n++) {
@@ -244,7 +244,7 @@ static int d_fill_buffer(struct demuxer *demuxer)
bool eos_reached = p->eos_packets > 0;
if (eos_reached && p->eos_packets < 100) {
for (int n = 0; n < p->num_streams; n++) {
- struct virtual_stream *vs = &p->streams[n];
+ struct virtual_stream *vs = p->streams[n];
if (vs->selected) {
int max_packets = 0;
if (vs->sh->type == STREAM_AUDIO)
@@ -276,6 +276,7 @@ static int d_fill_buffer(struct demuxer *demuxer)
goto drop;
if (!p->dash) {
+ pkt->segmented = true;
if (!pkt->codec)
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
@@ -293,7 +294,7 @@ static int d_fill_buffer(struct demuxer *demuxer)
if (pkt->pos >= 0)
pkt->pos |= (seg->index & 0x7FFFULL) << 48;
- struct virtual_stream *vs = &p->streams[pkt->stream];
+ struct virtual_stream *vs = p->streams[pkt->stream];
if (pkt->pts != MP_NOPTS_VALUE && pkt->pts >= seg->end) {
// Trust the keyframe flag. Might not always be a good idea, but will
@@ -307,10 +308,6 @@ static int d_fill_buffer(struct demuxer *demuxer)
}
}
- if (!p->dash)
- pkt->new_segment |= vs->new_segment;
- vs->new_segment = false;
-
demux_add_packet(vs->sh, pkt);
return 1;
@@ -377,8 +374,10 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
new->forced_track = sh->forced_track;
new->hls_bitrate = sh->hls_bitrate;
new->missing_timestamps = sh->missing_timestamps;
+ new->attached_picture = sh->attached_picture;
demux_add_sh_stream(demuxer, new);
- struct virtual_stream vs = {
+ struct virtual_stream *vs = talloc_ptrtype(p, vs);
+ *vs = (struct virtual_stream){
.sh = new,
};
MP_TARRAY_APPEND(p, p->streams, p->num_streams, vs);
@@ -388,6 +387,11 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
struct timeline_part *part = &p->tl->parts[n];
struct timeline_part *next = &p->tl->parts[n + 1];
+ // demux_timeline already does caching, doing it for the sub-demuxers
+ // would be pointless and wasteful.
+ if (part->source)
+ demux_disable_cache(part->source);
+
struct segment *seg = talloc_ptrtype(p, seg);
*seg = (struct segment){
.d = part->source,
diff --git a/demux/ebml.c b/demux/ebml.c
index e05f724..a64c1dd 100644
--- a/demux/ebml.c
+++ b/demux/ebml.c
@@ -74,83 +74,52 @@ uint32_t ebml_read_id(stream_t *s)
}
/*
- * Read a variable length unsigned int.
+ * Read: element content length.
*/
-uint64_t ebml_read_vlen_uint(bstr *buffer)
+uint64_t ebml_read_length(stream_t *s)
{
int i, j, num_ffs = 0, len_mask = 0x80;
- uint64_t num;
-
- if (buffer->len == 0)
- return EBML_UINT_INVALID;
+ uint64_t len;
- for (i = 0, num = buffer->start[0]; i < 8 && !(num & len_mask); i++)
+ for (i = 0, len = stream_read_char(s); i < 8 && !(len & len_mask); i++)
len_mask >>= 1;
if (i >= 8)
return EBML_UINT_INVALID;
j = i + 1;
- if ((int) (num &= (len_mask - 1)) == len_mask - 1)
+ if ((int) (len &= (len_mask - 1)) == len_mask - 1)
num_ffs++;
- if (j > buffer->len)
- return EBML_UINT_INVALID;
- for (int n = 0; n < i; n++) {
- num = (num << 8) | buffer->start[n + 1];
- if ((num & 0xFF) == 0xFF)
+ while (i--) {
+ len = (len << 8) | stream_read_char(s);
+ if ((len & 0xFF) == 0xFF)
num_ffs++;
}
if (j == num_ffs)
return EBML_UINT_INVALID;
- buffer->start += j;
- buffer->len -= j;
- return num;
+ if (len >= 1ULL<<63) // Can happen if stream_read_char returns EOF
+ return EBML_UINT_INVALID;
+ return len;
}
+
/*
* Read a variable length signed int.
*/
-int64_t ebml_read_vlen_int(bstr *buffer)
+int64_t ebml_read_signed_length(stream_t *s)
{
uint64_t unum;
int l;
/* read as unsigned number first */
- size_t len = buffer->len;
- unum = ebml_read_vlen_uint(buffer);
+ uint64_t offset = stream_tell(s);
+ unum = ebml_read_length(s);
if (unum == EBML_UINT_INVALID)
return EBML_INT_INVALID;
- l = len - buffer->len;
+ l = stream_tell(s) - offset;
return unum - ((1LL << ((7 * l) - 1)) - 1);
}
/*
- * Read: element content length.
- */
-uint64_t ebml_read_length(stream_t *s)
-{
- int i, j, num_ffs = 0, len_mask = 0x80;
- uint64_t len;
-
- for (i = 0, len = stream_read_char(s); i < 8 && !(len & len_mask); i++)
- len_mask >>= 1;
- if (i >= 8)
- return EBML_UINT_INVALID;
- j = i + 1;
- if ((int) (len &= (len_mask - 1)) == len_mask - 1)
- num_ffs++;
- while (i--) {
- len = (len << 8) | stream_read_char(s);
- if ((len & 0xFF) == 0xFF)
- num_ffs++;
- }
- if (j == num_ffs)
- return EBML_UINT_INVALID;
- if (len >= 1ULL<<63) // Can happen if stream_read_char returns EOF
- return EBML_UINT_INVALID;
- return len;
-}
-
-/*
* Read the next element as an unsigned int.
*/
uint64_t ebml_read_uint(stream_t *s)
@@ -509,8 +478,8 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
bool multiple = fd->multiple;
int *countptr = (int *) (s + fd->count_offset);
if (*countptr >= num_elems[field_idx]) {
- // Shouldn't happen with on any sane file without bugs
- MP_ERR(ctx, "Too many subelems?\n");
+ // Shouldn't happen on any sane file without bugs
+ MP_ERR(ctx, "Too many subelements.\n");
ctx->has_errors = true;
data += length;
continue;
diff --git a/demux/ebml.h b/demux/ebml.h
index 8b67ea7..86a4009 100644
--- a/demux/ebml.h
+++ b/demux/ebml.h
@@ -80,9 +80,8 @@ struct ebml_parse_ctx {
bool ebml_is_mkv_level1_id(uint32_t id);
uint32_t ebml_read_id (stream_t *s);
-uint64_t ebml_read_vlen_uint (bstr *buffer);
-int64_t ebml_read_vlen_int (bstr *buffer);
uint64_t ebml_read_length (stream_t *s);
+int64_t ebml_read_signed_length(stream_t *s);
uint64_t ebml_read_uint (stream_t *s);
int64_t ebml_read_int (stream_t *s);
int ebml_read_skip(struct mp_log *log, int64_t end, stream_t *s);
diff --git a/demux/packet.c b/demux/packet.c
index fd1754b..84fda8c 100644
--- a/demux/packet.c
+++ b/demux/packet.c
@@ -54,6 +54,7 @@ struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt)
.end = MP_NOPTS_VALUE,
.stream = -1,
.avpacket = talloc_zero(dp, AVPacket),
+ .kf_seek_pts = MP_NOPTS_VALUE,
};
av_init_packet(dp->avpacket);
int r = -1;
@@ -74,6 +75,17 @@ struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt)
return dp;
}
+// (buf must include proper padding)
+struct demux_packet *new_demux_packet_from_buf(struct AVBufferRef *buf)
+{
+ AVPacket pkt = {
+ .size = buf->size,
+ .data = buf->data,
+ .buf = buf,
+ };
+ return new_demux_packet_from_avpacket(&pkt);
+}
+
// Input data doesn't need to be padded.
struct demux_packet *new_demux_packet_from(void *data, size_t len)
{
@@ -109,9 +121,10 @@ void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *sr
dst->dts = src->dts;
dst->duration = src->duration;
dst->pos = src->pos;
+ dst->segmented = src->segmented;
dst->start = src->start;
dst->end = src->end;
- dst->new_segment = src->new_segment;
+ dst->codec = src->codec;
dst->keyframe = src->keyframe;
dst->stream = src->stream;
}
diff --git a/demux/packet.h b/demux/packet.h
index 884a7d8..f551e0c 100644
--- a/demux/packet.h
+++ b/demux/packet.h
@@ -36,18 +36,22 @@ typedef struct demux_packet {
int stream; // source stream index
// segmentation (ordered chapters, EDL)
- struct mp_codec_params *codec;
- double start, end;
- bool new_segment;
+ bool segmented;
+ struct mp_codec_params *codec; // set to non-NULL iff segmented is set
+ double start, end; // set to non-NOPTS iff segmented is set
// private
struct demux_packet *next;
struct AVPacket *avpacket; // keep the buffer allocation and sidedata
+ double kf_seek_pts; // demux.c internal: seek pts for keyframe range
} demux_packet_t;
+struct AVBufferRef;
+
struct demux_packet *new_demux_packet(size_t len);
struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt);
struct demux_packet *new_demux_packet_from(void *data, size_t len);
+struct demux_packet *new_demux_packet_from_buf(struct AVBufferRef *buf);
void demux_packet_shorten(struct demux_packet *dp, size_t len);
void free_demux_packet(struct demux_packet *dp);
struct demux_packet *demux_copy_packet(struct demux_packet *dp);
diff --git a/etc/builtin.conf b/etc/builtin.conf
index 1d93df9..8e954b9 100644
--- a/etc/builtin.conf
+++ b/etc/builtin.conf
@@ -20,7 +20,6 @@ osc=no
ytdl=no
input-default-bindings=no
input-vo-keyboard=no
-stop-playback-on-init-failure=yes
# OSX/Cocoa global input hooks
input-appleremote=no
input-media-keys=no
@@ -36,7 +35,7 @@ load-scripts=no
osc=no
framedrop=no
-[opengl-hq]
+[gpu-hq]
scale=spline36
cscale=spline36
dscale=mitchell
@@ -44,3 +43,7 @@ dither-depth=auto
correct-downscaling=yes
sigmoid-upscaling=yes
deband=yes
+
+# Compatibility alias (deprecated)
+[opengl-hq]
+profile=gpu-hq
diff --git a/etc/input.conf b/etc/input.conf
index b58d32a..130648c 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -78,6 +78,8 @@
#O no-osd cycle-values osd-level 3 1 # cycle through OSD mode
#o show-progress
#P show-progress
+#i script-binding stats/display-stats
+#I script-binding stats/display-stats-toggle
#z add sub-delay -0.1 # subtract 100 ms delay from subs
#x add sub-delay +0.1 # add
#ctrl++ add audio-delay 0.100 # this changes audio/video sync
@@ -140,6 +142,7 @@
#l ab-loop # Set/clear A-B loop points
#L cycle-values loop-file "inf" "no" # toggle infinite looping
#ctrl+c quit 4
+#DEL script-binding osc/visibility # cycle OSC display
# Apple Remote section
#AR_PLAY cycle pause
diff --git a/etc/mpv.conf b/etc/mpv.conf
index d72c9ee..2a8f8b9 100644
--- a/etc/mpv.conf
+++ b/etc/mpv.conf
@@ -52,9 +52,9 @@
# Keep the player window on top of all other windows.
#ontop=yes
-# Specify high quality video rendering preset (for OpenGL VO only)
+# Specify high quality video rendering preset (for --vo=gpu only)
# Can cause performance problems with some drivers and GPUs.
-#profile=opengl-hq
+#profile=gpu-hq
# Force video to lock on the display's refresh rate, and change video and audio
# speed to some degree to ensure synchronous playback - can cause problems
diff --git a/etc/restore-old-bindings.conf b/etc/restore-old-bindings.conf
index d09051e..662a699 100644
--- a/etc/restore-old-bindings.conf
+++ b/etc/restore-old-bindings.conf
@@ -9,6 +9,13 @@
#
# Older installations use ~/.mpv/input.conf instead.
+# changed in mpv 0.27.0 (macOS and Wayland only)
+
+# WHEEL_UP seek 10
+# WHEEL_DOWN seek -10
+# WHEEL_LEFT seek 5
+# WHEEL_RIGHT seek -5
+
# changed in mpv 0.26.0
h cycle tv-channel -1 # previous channel
diff --git a/input/cmd_list.c b/input/cmd_list.c
index 6199e7c..333f2cb 100644
--- a/input/cmd_list.c
+++ b/input/cmd_list.c
@@ -13,8 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#include <limits.h>
@@ -103,12 +101,10 @@ const struct mp_cmd_def mp_cmds[] = {
.allow_auto_repeat = true},
{ MP_CMD_EXPAND_TEXT, "expand-text", { ARG_STRING } },
{ MP_CMD_SHOW_PROGRESS, "show-progress", .allow_auto_repeat = true},
-#if HAVE_GPL
{ MP_CMD_SUB_ADD, "sub-add", { ARG_STRING,
OARG_CHOICE(0, ({"select", 0}, {"auto", 1}, {"cached", 2})),
OARG_STRING(""), OARG_STRING("") } },
{ MP_CMD_SUB_REMOVE, "sub-remove", { OARG_INT(-1) } },
-#endif
{ MP_CMD_SUB_RELOAD, "sub-reload", { OARG_INT(-1) } },
{ MP_CMD_TV_LAST_CHANNEL, "tv-last-channel", },
@@ -153,7 +149,6 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_RUN, "run", { ARG_STRING, ARG_STRING }, .vararg = true },
{ MP_CMD_SET, "set", { ARG_STRING, ARG_STRING } },
-#if HAVE_GPL
{ MP_CMD_ADD, "add", { ARG_STRING, OARG_DOUBLE(1) },
.allow_auto_repeat = true,
.scalable = true,
@@ -165,7 +160,6 @@ const struct mp_cmd_def mp_cmds[] = {
.allow_auto_repeat = true,
.scalable = true,
},
-#endif
{ MP_CMD_MULTIPLY, "multiply", { ARG_STRING, ARG_DOUBLE },
.allow_auto_repeat = true},
diff --git a/input/input.c b/input/input.c
index b3f3d8d..14a4def 100644
--- a/input/input.c
+++ b/input/input.c
@@ -468,7 +468,7 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
return mp_input_parse_cmd_strv(ictx->log, (const char*[]){"quit", 0});
int msgl = MSGL_WARN;
if (MP_KEY_IS_MOUSE_MOVE(code))
- msgl = MSGL_DEBUG;
+ msgl = MSGL_TRACE;
char *key_buf = mp_input_get_key_combo_name(&code, 1);
MP_MSG(ictx, msgl, "No key binding found for key '%s'.\n", key_buf);
talloc_free(key_buf);
@@ -478,8 +478,8 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
if (ret) {
ret->input_section = cmd->owner->section;
ret->key_name = talloc_steal(ret, mp_input_get_key_combo_name(&code, 1));
- MP_DBG(ictx, "key '%s' -> '%s' in '%s'\n",
- ret->key_name, cmd->cmd, ret->input_section);
+ MP_TRACE(ictx, "key '%s' -> '%s' in '%s'\n",
+ ret->key_name, cmd->cmd, ret->input_section);
ret->is_mouse_button = code & MP_KEY_EMIT_ON_UP;
} else {
char *key_buf = mp_input_get_key_combo_name(&code, 1);
@@ -501,8 +501,8 @@ static void update_mouse_section(struct input_ctx *ictx)
ictx->mouse_section = new_section;
if (strcmp(old, ictx->mouse_section) != 0) {
- MP_DBG(ictx, "input: switch section %s -> %s\n",
- old, ictx->mouse_section);
+ MP_TRACE(ictx, "input: switch section %s -> %s\n",
+ old, ictx->mouse_section);
mp_input_queue_cmd(ictx, get_cmd_from_keys(ictx, old, MP_KEY_MOUSE_LEAVE));
}
}
@@ -561,9 +561,9 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale,
if (mp_msg_test(ictx->log, MSGL_DEBUG)) {
char *key = mp_input_get_key_name(code);
- MP_DBG(ictx, "key code=%#x '%s'%s%s\n",
- code, key, (state & MP_KEY_STATE_DOWN) ? " down" : "",
- (state & MP_KEY_STATE_UP) ? " up" : "");
+ MP_TRACE(ictx, "key code=%#x '%s'%s%s\n",
+ code, key, (state & MP_KEY_STATE_DOWN) ? " down" : "",
+ (state & MP_KEY_STATE_UP) ? " up" : "");
talloc_free(key);
}
@@ -708,7 +708,7 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale,
code = mp_normalize_keycode(code);
int unmod = code & ~MP_KEY_MODIFIER_MASK;
if (code == MP_INPUT_RELEASE_ALL) {
- MP_DBG(ictx, "release all\n");
+ MP_TRACE(ictx, "release all\n");
release_down_cmd(ictx, false);
return;
}
@@ -816,7 +816,7 @@ void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y)
void mp_input_set_mouse_pos_artificial(struct input_ctx *ictx, int x, int y)
{
input_lock(ictx);
- MP_DBG(ictx, "mouse move %d/%d\n", x, y);
+ MP_TRACE(ictx, "mouse move %d/%d\n", x, y);
if (ictx->mouse_vo_x == x && ictx->mouse_vo_y == y) {
input_unlock(ictx);
@@ -832,7 +832,7 @@ void mp_input_set_mouse_pos_artificial(struct input_ctx *ictx, int x, int y)
x = x * 1.0 / (dst->x1 - dst->x0) * (src->x1 - src->x0) + src->x0;
y = y * 1.0 / (dst->y1 - dst->y0) * (src->y1 - src->y0) + src->y0;
}
- MP_DBG(ictx, "-> %d/%d\n", x, y);
+ MP_TRACE(ictx, "-> %d/%d\n", x, y);
}
ictx->mouse_event_counter++;
diff --git a/input/ipc-unix.c b/input/ipc-unix.c
index c3315d2..3b01d47 100644
--- a/input/ipc-unix.c
+++ b/input/ipc-unix.c
@@ -216,16 +216,26 @@ done:
static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
{
- client->client = mp_new_client(ctx->client_api, client->client_name),
- client->log = mp_client_get_log(client->client);
+ client->client = mp_new_client(ctx->client_api, client->client_name);
+ if (!client->client)
+ goto err;
+
+ client->log = mp_client_get_log(client->client);
pthread_t client_thr;
- if (pthread_create(&client_thr, NULL, client_thread, client)) {
+ if (pthread_create(&client_thr, NULL, client_thread, client))
+ goto err;
+
+ return;
+
+err:
+ if (client->client)
mpv_detach_destroy(client->client);
- if (client->close_client_fd)
- close(client->client_fd);
- talloc_free(client);
- }
+
+ if (client->close_client_fd)
+ close(client->client_fd);
+
+ talloc_free(client);
}
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
diff --git a/libmpv/client.h b/libmpv/client.h
index eca0978..77e7ce0 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -205,7 +205,7 @@ extern "C" {
* relational operators (<, >, <=, >=).
*/
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
-#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 25)
+#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 26)
/**
* The API user is allowed to "#define MPV_ENABLE_DEPRECATED 0" before
diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h
index 799e353..ccafbf5 100644
--- a/libmpv/opengl_cb.h
+++ b/libmpv/opengl_cb.h
@@ -116,17 +116,18 @@ extern "C" {
*
* While "normal" mpv loads the OpenGL hardware decoding interop on demand,
* this can't be done with opengl_cb for internal technical reasons. Instead,
- * make it load the interop at load time by setting the
- * "opengl-hwdec-interop"="auto" option before calling mpv_opengl_cb_init_gl()
- * ("hwdec-preload" in older mpv releases).
+ * it loads them by default, even if hardware decoding is not going to be used.
+ * In older mpv relases, this had to be done by setting the
+ * "opengl-hwdec-interop" or "hwdec-preload" options before calling
+ * mpv_opengl_cb_init_gl(). You can still use the newer "gpu-hwdec-interop"
+ * option to prevent loading of interop, or to load only a specific interop.
*
* There may be certain requirements on the OpenGL implementation:
* - Windows: ANGLE is required (although in theory GL/DX interop could be used)
* - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
* must be provided (see sections below)
- * - nVidia/Linux: GLX is required (if you force "cuda", it should work on EGL
- * as well, if you have recent enough drivers and the
- * "hwaccel" option is set to "cuda" as well)
+ * - nVidia/Linux: Both GLX and EGL should work (GLX is required if vdpau is
+ * used, e.g. due to old drivers.)
* - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
* - iOS: EAGL is required (EAGLContext.currentContext returning non-nil)
*
@@ -151,6 +152,27 @@ extern "C" {
* up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything
* you know/expected, return NULL from the function.
*
+ * * Windowing system scaling
+ * ------------------------------------
+ *
+ * When using GL, sometimes GL rendering window is upscaled to display buffer.
+ * Typically with drm where GL framebuffer can be upscaled at later stage.
+ * In That case glMPGetNativeDisplay("opengl-cb-window-pos") should return an
+ * mpv_opengl_cb_window_pos struct pointer defined below.
+ * Note : The intended use is for hardware overlays that might require
+ * upscaling features (typically upscaling GL windows with drm to screen size).
+ *
+ * This is never used for GL rendering - only to map hardware overlays to
+ * GL rendering (for backends which support it).
+ */
+struct mpv_opengl_cb_window_pos {
+ int x; // left coordinates of window (usually 0)
+ int y; // top coordinates of window (usually 0)
+ int width; // width of GL window
+ int height; // height of GL window
+};
+
+/**
* Windowing system interop on Intel/Linux with VAAPI
* --------------------------------------------------
*
@@ -163,10 +185,22 @@ extern "C" {
*
* glMPGetNativeDisplay("wl") should return a Wayland "struct wl_display *".
*
- * glMPGetNativeDisplay("drm") should return a DRM FD casted to intptr_t (note
- * that a 0 FD is not supported - if this can happen in your case, you must
- * dup2() it to a non-0 FD).
- *
+ * glMPGetNativeDisplay("opengl-cb-drm-params") should return an
+ * mpv_opengl_cb_drm_params structure pointer :
+ */
+struct mpv_opengl_cb_drm_params {
+ // DRM fd (int). set this to -1 if invalid.
+ int fd;
+
+ // currently used crtc id
+ int crtc_id;
+
+ // pointer to the drmModeAtomicReq that is being used for the renderloop.
+ // This atomic request pointer should be usually created at every renderloop.
+ struct _drmModeAtomicReq *atomic_request;
+};
+
+/**
* nVidia/Linux via VDPAU requires GLX, which does not have this problem (the
* GLX API can return the current X11 Display).
*
diff --git a/misc/json.c b/misc/json.c
index 56d440f..4797fde 100644
--- a/misc/json.c
+++ b/misc/json.c
@@ -235,7 +235,16 @@ static void write_json_str(bstr *b, unsigned char *str)
APPEND(b, "\"");
}
-static int json_append(bstr *b, const struct mpv_node *src)
+static void add_indent(bstr *b, int indent)
+{
+ if (indent < 0)
+ return;
+ bstr_xappend(NULL, b, bstr0("\n"));
+ for (int n = 0; n < indent; n++)
+ bstr_xappend(NULL, b, bstr0(" "));
+}
+
+static int json_append(bstr *b, const struct mpv_node *src, int indent)
{
switch (src->format) {
case MPV_FORMAT_NONE:
@@ -258,15 +267,18 @@ static int json_append(bstr *b, const struct mpv_node *src)
struct mpv_node_list *list = src->u.list;
bool is_obj = src->format == MPV_FORMAT_NODE_MAP;
APPEND(b, is_obj ? "{" : "[");
+ int next_indent = indent >= 0 ? indent + 1 : -1;
for (int n = 0; n < list->num; n++) {
if (n)
APPEND(b, ",");
+ add_indent(b, next_indent);
if (is_obj) {
write_json_str(b, list->keys[n]);
APPEND(b, ":");
}
- json_append(b, &list->values[n]);
+ json_append(b, &list->values[n], next_indent);
}
+ add_indent(b, indent);
APPEND(b, is_obj ? "}" : "]");
return 0;
}
@@ -274,6 +286,14 @@ static int json_append(bstr *b, const struct mpv_node *src)
return -1; // unknown format
}
+static int json_append_str(char **dst, struct mpv_node *src, int indent)
+{
+ bstr buffer = bstr0(*dst);
+ int r = json_append(&buffer, src, indent);
+ *dst = buffer.start;
+ return r;
+}
+
/* Write the contents of *src as JSON, and append the JSON string to *dst.
* This will use strlen() to determine the start offset, and ta_get_size()
* and ta_realloc() to extend the memory allocation of *dst.
@@ -281,8 +301,11 @@ static int json_append(bstr *b, const struct mpv_node *src)
*/
int json_write(char **dst, struct mpv_node *src)
{
- bstr buffer = bstr0(*dst);
- int r = json_append(&buffer, src);
- *dst = buffer.start;
- return r;
+ return json_append_str(dst, src, -1);
+}
+
+// Same as json_write(), but add whitespace to make it readable.
+int json_write_pretty(char **dst, struct mpv_node *src)
+{
+ return json_append_str(dst, src, 0);
}
diff --git a/misc/json.h b/misc/json.h
index 0a9f8d7..43d565b 100644
--- a/misc/json.h
+++ b/misc/json.h
@@ -24,5 +24,6 @@
int json_parse(void *ta_parent, struct mpv_node *dst, char **src, int max_depth);
void json_skip_whitespace(char **src);
int json_write(char **s, struct mpv_node *src);
+int json_write_pretty(char **s, struct mpv_node *src);
#endif
diff --git a/misc/node.c b/misc/node.c
index 1ea8ea7..73e95e6 100644
--- a/misc/node.c
+++ b/misc/node.c
@@ -63,3 +63,18 @@ void node_map_add_string(struct mpv_node *dst, const char *key, const char *val)
entry->format = MPV_FORMAT_STRING;
entry->u.string = talloc_strdup(dst->u.list, val);
}
+
+void node_map_add_int64(struct mpv_node *dst, const char *key, int64_t v)
+{
+ node_map_add(dst, key, MPV_FORMAT_INT64)->u.int64 = v;
+}
+
+void node_map_add_double(struct mpv_node *dst, const char *key, double v)
+{
+ node_map_add(dst, key, MPV_FORMAT_DOUBLE)->u.double_ = v;
+}
+
+void node_map_add_flag(struct mpv_node *dst, const char *key, bool v)
+{
+ node_map_add(dst, key, MPV_FORMAT_FLAG)->u.flag = v;
+}
diff --git a/misc/node.h b/misc/node.h
index c3b4501..a1bdab0 100644
--- a/misc/node.h
+++ b/misc/node.h
@@ -7,5 +7,8 @@ void node_init(struct mpv_node *dst, int format, struct mpv_node *parent);
struct mpv_node *node_array_add(struct mpv_node *dst, int format);
struct mpv_node *node_map_add(struct mpv_node *dst, const char *key, int format);
void node_map_add_string(struct mpv_node *dst, const char *key, const char *val);
+void node_map_add_int64(struct mpv_node *dst, const char *key, int64_t v);
+void node_map_add_double(struct mpv_node *dst, const char *key, double v);
+void node_map_add_flag(struct mpv_node *dst, const char *key, bool v);
#endif
diff --git a/options/m_config.c b/options/m_config.c
index 24f4b83..6f22fd5 100644
--- a/options/m_config.c
+++ b/options/m_config.c
@@ -549,8 +549,9 @@ struct m_config_option *m_config_get_co_raw(const struct m_config *config,
return NULL;
}
-struct m_config_option *m_config_get_co(const struct m_config *config,
- struct bstr name)
+// Like m_config_get_co_raw(), but resolve aliases.
+static struct m_config_option *m_config_get_co_any(const struct m_config *config,
+ struct bstr name)
{
struct m_config_option *co = m_config_get_co_raw(config, name);
if (!co)
@@ -571,10 +572,7 @@ struct m_config_option *m_config_get_co(const struct m_config *config,
}
co->warning_was_printed = true;
}
- return m_config_get_co(config, bstr0(alias));
- } else if (co->opt->type == &m_option_type_cli_alias) {
- // Pretend it does not exist.
- return NULL;
+ return m_config_get_co_any(config, bstr0(alias));
} else if (co->opt->type == &m_option_type_removed) {
if (!co->warning_was_printed) {
char *msg = co->opt->priv;
@@ -599,6 +597,17 @@ struct m_config_option *m_config_get_co(const struct m_config *config,
return co;
}
+struct m_config_option *m_config_get_co(const struct m_config *config,
+ struct bstr name)
+{
+ struct m_config_option *co = m_config_get_co_any(config, name);
+ // CLI aliases should not be real options, and are explicitly handled by
+ // m_config_set_option_cli(). So petend it does not exist.
+ if (co && co->opt->type == &m_option_type_cli_alias)
+ co = NULL;
+ return co;
+}
+
int m_config_get_co_count(struct m_config *config)
{
return config->num_opts;
@@ -818,7 +827,7 @@ static struct m_config_option *m_config_mogrify_cli_opt(struct m_config *config,
}
// Resolve CLI alias. (We don't allow you to combine them with "--no-".)
- co = m_config_get_co_raw(config, *name);
+ co = m_config_get_co_any(config, *name);
if (co && co->opt->type == &m_option_type_cli_alias)
*name = bstr0((char *)co->opt->priv);
@@ -826,19 +835,25 @@ static struct m_config_option *m_config_mogrify_cli_opt(struct m_config *config,
// matches. (We don't allow you to combine them with "--no-".)
for (int n = 0; n < config->num_opts; n++) {
co = &config->opts[n];
- const struct m_option_type *type = co->opt->type;
- struct bstr coname = bstr0(co->name);
+ struct bstr basename = bstr0(co->name);
- if (!bstr_startswith(*name, coname))
+ if (!bstr_startswith(*name, basename))
continue;
+ // Aliased option + a suffix action, e.g. --opengl-shaders-append
+ if (co->opt->type == &m_option_type_alias)
+ co = m_config_get_co_any(config, basename);
+ if (!co)
+ continue;
+
+ const struct m_option_type *type = co->opt->type;
for (int i = 0; type->actions && type->actions[i].name; i++) {
const struct m_option_action *action = &type->actions[i];
bstr suffix = bstr0(action->name);
if (bstr_endswith(*name, suffix) &&
- (name->len == coname.len + 1 + suffix.len) &&
- name->start[coname.len] == '-')
+ (name->len == basename.len + 1 + suffix.len) &&
+ name->start[basename.len] == '-')
{
*out_add_flags = action->flags;
return co;
@@ -885,8 +900,8 @@ int m_config_set_option_cli(struct m_config *config, struct bstr name,
goto done;
if (r == 2) {
- MP_VERBOSE(config, "Setting option '%.*s' = '%.*s' (flags = %d)\n",
- BSTR_P(name), BSTR_P(param), flags);
+ MP_DBG(config, "Setting option '%.*s' = '%.*s' (flags = %d)\n",
+ BSTR_P(name), BSTR_P(param), flags);
}
union m_option_value val = {0};
@@ -937,8 +952,8 @@ int m_config_set_option_node(struct m_config *config, bstr name,
if (mp_msg_test(config->log, MSGL_V)) {
char *s = m_option_type_node.print(NULL, data);
- MP_VERBOSE(config, "Setting option '%.*s' = %s (flags = %d) -> %d\n",
- BSTR_P(name), s ? s : "?", flags, r);
+ MP_DBG(config, "Setting option '%.*s' = %s (flags = %d) -> %d\n",
+ BSTR_P(name), s ? s : "?", flags, r);
talloc_free(s);
}
diff --git a/options/m_option.c b/options/m_option.c
index 0ec8582..08f57f8 100644
--- a/options/m_option.c
+++ b/options/m_option.c
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
/// \file
@@ -137,13 +135,18 @@ static int parse_flag(struct mp_log *log, const m_option_t *opt,
VAL(dst) = 0;
return 1;
}
- mp_fatal(log, "Invalid parameter for %.*s flag: %.*s\n",
- BSTR_P(name), BSTR_P(param));
- mp_info(log, "Valid values are:\n");
+ bool is_help = bstr_equals0(param, "help");
+ if (is_help) {
+ mp_info(log, "Valid values for %.*s flag are:\n", BSTR_P(name));
+ } else {
+ mp_fatal(log, "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_info(log, "Valid values are:\n");
+ }
mp_info(log, " yes\n");
mp_info(log, " no\n");
mp_info(log, " (passing nothing)\n");
- return M_OPT_INVALID;
+ return is_help ? M_OPT_EXIT : M_OPT_INVALID;
}
static char *print_flag(const m_option_t *opt, const void *val)
@@ -443,6 +446,15 @@ const char *m_opt_choice_str(const struct m_opt_choice_alternatives *choices,
return NULL;
}
+static void print_choice_values(struct mp_log *log, const struct m_option *opt)
+{
+ struct m_opt_choice_alternatives *alt = opt->priv;
+ for ( ; alt->name; alt++)
+ mp_info(log, " %s\n", alt->name[0] ? alt->name : "(passing nothing)");
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX))
+ mp_info(log, " %g-%g (integer range)\n", opt->min, opt->max);
+}
+
static int parse_choice(struct mp_log *log, const struct m_option *opt,
struct bstr name, struct bstr param, void *dst)
{
@@ -459,6 +471,11 @@ static int parse_choice(struct mp_log *log, const struct m_option *opt,
}
}
if (!alt->name) {
+ if (!bstrcmp0(param, "help")) {
+ mp_info(log, "Valid values for option %.*s are:\n", BSTR_P(name));
+ print_choice_values(log, opt);
+ return M_OPT_EXIT;
+ }
if (param.len == 0)
return M_OPT_MISSING_PARAM;
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
@@ -472,10 +489,7 @@ static int parse_choice(struct mp_log *log, const struct m_option *opt,
mp_fatal(log, "Invalid value for option %.*s: %.*s\n",
BSTR_P(name), BSTR_P(param));
mp_info(log, "Valid values are:\n");
- for (alt = opt->priv; alt->name; alt++)
- mp_info(log, " %s\n", alt->name[0] ? alt->name : "(passing nothing)");
- if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX))
- mp_info(log, " %g-%g (integer range)\n", opt->min, opt->max);
+ print_choice_values(log, opt);
return M_OPT_INVALID;
}
if (dst)
@@ -997,9 +1011,6 @@ static int clamp_str(const m_option_t *opt, void *val)
static int parse_str(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
- if (param.start == NULL)
- return M_OPT_MISSING_PARAM;
-
m_opt_string_validate_fn validate = opt->priv;
if (validate) {
int r = validate(log, opt, name, param);
@@ -1735,17 +1746,21 @@ static int parse_color(struct mp_log *log, const m_option_t *opt,
if (param.len == 0)
return M_OPT_MISSING_PARAM;
+ bool is_help = bstr_equals0(param, "help");
+ if (is_help)
+ goto exit;
+
bstr val = param;
struct m_color color = {0};
if (bstr_eatstart0(&val, "#")) {
// #[AA]RRGGBB
if (val.len != 6 && val.len != 8)
- goto error;
+ goto exit;
bool has_alpha = val.len == 8;
uint32_t c = bstrtoll(val, &val, 16);
if (val.len)
- goto error;
+ goto exit;
color = (struct m_color) {
(c >> 16) & 0xFF,
(c >> 8) & 0xFF,
@@ -1756,13 +1771,13 @@ static int parse_color(struct mp_log *log, const m_option_t *opt,
bstr comp_str[5];
int num = split_char(param, '/', 5, comp_str);
if (num < 1 || num > 4)
- goto error;
+ goto exit;
double comp[4] = {0, 0, 0, 1};
for (int n = 0; n < num; n++) {
bstr rest;
double d = bstrtod(comp_str[n], &rest);
if (rest.len || !comp_str[n].len || d < 0 || d > 1 || !isfinite(d))
- goto error;
+ goto exit;
comp[n] = d;
}
if (num == 2)
@@ -1778,13 +1793,15 @@ static int parse_color(struct mp_log *log, const m_option_t *opt,
return 1;
-error:
- mp_err(log, "Option %.*s: invalid color: '%.*s'\n",
- BSTR_P(name), BSTR_P(param));
- mp_err(log, "Valid colors must be in the form #RRGGBB or #AARRGGBB (in hex)\n"
- "Or in the form 'r/g/b/a', where each component is a value in the\n"
- "range 0.0-1.0. (Also allowed: 'gray', 'gray/a', 'r/g/b'.\n");
- return M_OPT_INVALID;
+exit:
+ if (!is_help) {
+ mp_err(log, "Option %.*s: invalid color: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ }
+ mp_info(log, "Valid colors must be in the form #RRGGBB or #AARRGGBB (in hex)\n"
+ "or in the form 'r/g/b/a', where each component is a value in the\n"
+ "range 0.0-1.0. (Also allowed: 'gray', 'gray/a', 'r/g/b').\n");
+ return is_help ? M_OPT_EXIT : M_OPT_INVALID;
}
const m_option_type_t m_option_type_color = {
@@ -1945,20 +1962,27 @@ void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
static int parse_geometry(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
+ bool is_help = bstr_equals0(param, "help");
+ if (is_help)
+ goto exit;
+
struct m_geometry gm;
if (!parse_geometry_str(&gm, param))
- goto error;
+ goto exit;
if (dst)
*((struct m_geometry *)dst) = gm;
return 1;
-error:
- mp_err(log, "Option %.*s: invalid geometry: '%.*s'\n",
- BSTR_P(name), BSTR_P(param));
- mp_err(log, "Valid format: [W[%%][xH[%%]]][{+-}X[%%]{+-}Y[%%]] | [X[%%]:Y[%%]]\n");
- return M_OPT_INVALID;
+exit:
+ if (!is_help) {
+ mp_err(log, "Option %.*s: invalid geometry: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ }
+ mp_info(log,
+ "Valid format: [W[%%][xH[%%]]][{+-}X[%%]{+-}Y[%%]] | [X[%%]:Y[%%]]\n");
+ return is_help ? M_OPT_EXIT : M_OPT_INVALID;
}
const m_option_type_t m_option_type_geometry = {
@@ -1972,23 +1996,29 @@ const m_option_type_t m_option_type_geometry = {
static int parse_size_box(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
+ bool is_help = bstr_equals0(param, "help");
+ if (is_help)
+ goto exit;
+
struct m_geometry gm;
if (!parse_geometry_str(&gm, param))
- goto error;
+ goto exit;
if (gm.xy_valid)
- goto error;
+ goto exit;
if (dst)
*((struct m_geometry *)dst) = gm;
return 1;
-error:
- mp_err(log, "Option %.*s: invalid size: '%.*s'\n",
- BSTR_P(name), BSTR_P(param));
- mp_err(log, "Valid format: W[%%][xH[%%]] or empty string\n");
- return M_OPT_INVALID;
+exit:
+ if (!is_help) {
+ mp_err(log, "Option %.*s: invalid size: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ }
+ mp_info(log, "Valid format: W[%%][xH[%%]] or empty string\n");
+ return is_help ? M_OPT_EXIT : M_OPT_INVALID;
}
const m_option_type_t m_option_type_size_box = {
@@ -2329,6 +2359,11 @@ static int parse_rel_time(struct mp_log *log, const m_option_t *opt,
if (param.len == 0)
return M_OPT_MISSING_PARAM;
+ if (bstr_equals0(param, "none")) {
+ t.type = REL_TIME_NONE;
+ goto out;
+ }
+
// Percent pos
if (bstr_endswith0(param, "%")) {
double percent = bstrtod(bstr_splice(param, 0, -1), &param);
@@ -3251,6 +3286,16 @@ static char *print_node(const m_option_t *opt, const void *val)
return t;
}
+static char *pretty_print_node(const m_option_t *opt, const void *val)
+{
+ char *t = talloc_strdup(NULL, "");
+ if (json_write_pretty(&t, &VAL(val)) < 0) {
+ talloc_free(t);
+ t = NULL;
+ }
+ return t;
+}
+
static void dup_node(void *ta_parent, struct mpv_node *node)
{
switch (node->format) {
@@ -3344,6 +3389,7 @@ const m_option_type_t m_option_type_node = {
.size = sizeof(struct mpv_node),
.parse = parse_node,
.print = print_node,
+ .pretty_print = pretty_print_node,
.copy = copy_node,
.free = free_node,
.set = node_set,
diff --git a/options/m_option.h b/options/m_option.h
index ad6dfe6..d61fde9 100644
--- a/options/m_option.h
+++ b/options/m_option.h
@@ -400,7 +400,7 @@ struct m_option {
#define UPDATE_TERM (1 << 7) // terminal options
#define UPDATE_DEINT (1 << 8) // --deinterlace
#define UPDATE_OSD (1 << 10) // related to OSD rendering
-#define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/ytdl
+#define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/ytdl/stats
#define UPDATE_IMGPAR (1 << 12) // video image params overrides
#define UPDATE_INPUT (1 << 13) // mostly --input-* options
#define UPDATE_AUDIO (1 << 14) // --audio-channels etc.
@@ -638,10 +638,10 @@ extern const char m_option_path_separator;
#define OPT_CHOICE_(optname, varname, flags, choices, ...) \
OPT_GENERAL(int, optname, varname, flags, M_CHOICES(choices), __VA_ARGS__)
// Variant which takes a pointer to struct m_opt_choice_alternatives directly
-#define OPT_CHOICE_C(optname, varname, flags, choices) \
+#define OPT_CHOICE_C(optname, varname, flags, choices, ...) \
OPT_GENERAL(int, optname, varname, flags, .priv = (void *) \
MP_EXPECT_TYPE(const struct m_opt_choice_alternatives*, choices), \
- .type = &m_option_type_choice)
+ .type = &m_option_type_choice, __VA_ARGS__)
#define OPT_FLAGS(...) \
OPT_CHOICE_(__VA_ARGS__, .type = &m_option_type_flags)
@@ -670,8 +670,9 @@ extern const char m_option_path_separator;
#define OPT_SIZE_BOX(...) \
OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_size_box)
-#define OPT_TRACKCHOICE(name, var) \
- OPT_CHOICE_OR_INT(name, var, 0, 0, 8190, ({"no", -2}, {"auto", -1}))
+#define OPT_TRACKCHOICE(name, var, ...) \
+ OPT_CHOICE_OR_INT(name, var, 0, 0, 8190, ({"no", -2}, {"auto", -1}), \
+ ## __VA_ARGS__)
#define OPT_ASPECT(...) \
OPT_GENERAL(float, __VA_ARGS__, .type = &m_option_type_aspect)
diff --git a/options/m_property.h b/options/m_property.h
index 35d704a..d71ec03 100644
--- a/options/m_property.h
+++ b/options/m_property.h
@@ -200,6 +200,8 @@ struct m_sub_property {
.type = {.type = CONF_TYPE_DOUBLE}, .value = {.double_ = (f)}
#define SUB_PROP_FLAG(f) \
.type = {.type = CONF_TYPE_FLAG}, .value = {.flag = (f)}
+#define SUB_PROP_PTS(f) \
+ .type = {.type = &m_option_type_time}, .value = {.double_ = (f)}
int m_property_read_sub(const struct m_sub_property *props, int action, void *arg);
diff --git a/options/options.c b/options/options.c
index e747388..af55529 100644
--- a/options/options.c
+++ b/options/options.c
@@ -13,8 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#ifndef MPLAYER_CFG_MPLAYER_H
@@ -44,18 +42,17 @@
#include "video/hwdec.h"
#include "video/image_writer.h"
#include "sub/osd.h"
-#include "audio/filter/af.h"
#include "audio/decode/dec_audio.h"
#include "player/core.h"
#include "player/command.h"
#include "stream/stream.h"
-#if HAVE_DRM
-#include "video/out/drm_common.h"
+#if HAVE_LIBAF
+#include "audio/filter/af.h"
#endif
-#if HAVE_GL
-#include "video/out/opengl/hwdec.h"
+#if HAVE_DRM
+#include "video/out/drm_common.h"
#endif
static void print_version(struct mp_log *log)
@@ -69,6 +66,7 @@ extern const struct m_sub_options stream_dvb_conf;
extern const struct m_sub_options stream_lavf_conf;
extern const struct m_sub_options stream_cache_conf;
extern const struct m_sub_options sws_conf;
+extern const struct m_sub_options drm_conf;
extern const struct m_sub_options demux_rawaudio_conf;
extern const struct m_sub_options demux_rawvideo_conf;
extern const struct m_sub_options demux_lavf_conf;
@@ -87,33 +85,14 @@ extern const struct m_obj_list af_obj_list;
extern const struct m_obj_list vo_obj_list;
extern const struct m_obj_list ao_obj_list;
+extern const struct m_sub_options opengl_conf;
+extern const struct m_sub_options vulkan_conf;
+extern const struct m_sub_options spirv_conf;
+extern const struct m_sub_options d3d11_conf;
+extern const struct m_sub_options d3d11va_conf;
extern const struct m_sub_options angle_conf;
extern const struct m_sub_options cocoa_conf;
-const struct m_opt_choice_alternatives mp_hwdec_names[] = {
- {"no", HWDEC_NONE},
- {"auto", HWDEC_AUTO},
- {"yes" , HWDEC_AUTO},
- {"auto-copy", HWDEC_AUTO_COPY},
- {"vdpau", HWDEC_VDPAU},
- {"vdpau-copy", HWDEC_VDPAU_COPY},
- {"videotoolbox",HWDEC_VIDEOTOOLBOX},
- {"videotoolbox-copy",HWDEC_VIDEOTOOLBOX_COPY},
- {"vaapi", HWDEC_VAAPI},
- {"vaapi-copy", HWDEC_VAAPI_COPY},
- {"dxva2", HWDEC_DXVA2},
- {"dxva2-copy", HWDEC_DXVA2_COPY},
- {"d3d11va", HWDEC_D3D11VA},
- {"d3d11va-copy",HWDEC_D3D11VA_COPY},
- {"rpi", HWDEC_RPI},
- {"rpi-copy", HWDEC_RPI_COPY},
- {"mediacodec", HWDEC_MEDIACODEC},
- {"cuda", HWDEC_CUDA},
- {"cuda-copy", HWDEC_CUDA_COPY},
- {"crystalhd", HWDEC_CRYSTALHD},
- {0}
-};
-
static const struct m_sub_options screenshot_conf = {
.opts = image_writer_opts,
.size = sizeof(struct image_writer_opts),
@@ -173,14 +152,7 @@ static const m_option_t mp_vo_opt_list[] = {
OPT_STRING("vo-mmcss-profile", mmcss_profile, 0),
#endif
#if HAVE_DRM
- OPT_STRING_VALIDATE("drm-connector", drm_connector_spec,
- 0, drm_validate_connector_opt),
- OPT_INT("drm-mode", drm_mode_id, 0),
-#endif
-#if HAVE_GL
- OPT_STRING_VALIDATE("opengl-hwdec-interop", gl_hwdec_interop, 0,
- ra_hwdec_validate_opt),
- OPT_REPLACED("hwdec-preload", "opengl-hwdec-interop"),
+ OPT_SUBSTRUCT("", drm_opts, drm_conf, 0),
#endif
{0}
};
@@ -301,6 +273,7 @@ const m_option_t mp_opts[] = {
OPT_FLAG("ytdl", lua_load_ytdl, UPDATE_BUILTIN_SCRIPTS),
OPT_STRING("ytdl-format", lua_ytdl_format, 0),
OPT_KEYVALUELIST("ytdl-raw-options", lua_ytdl_raw_options, 0),
+ OPT_FLAG("load-stats-overlay", lua_load_stats, UPDATE_BUILTIN_SCRIPTS),
#endif
// ------------------------- stream options --------------------
@@ -320,10 +293,7 @@ const m_option_t mp_opts[] = {
// ------------------------- demuxer options --------------------
-#if HAVE_GPL
- // Possibly GPL due to d8fd7131bbcde029ab41799fd3162050b43f6848.
OPT_CHOICE_OR_INT("frames", play_frames, 0, 0, INT_MAX, ({"all", -1})),
-#endif
OPT_REL_TIME("start", play_start, 0),
OPT_REL_TIME("end", play_end, 0),
@@ -353,14 +323,18 @@ const m_option_t mp_opts[] = {
OPT_TRACKCHOICE("vid", stream_id[0][STREAM_VIDEO]),
OPT_TRACKCHOICE("sid", stream_id[0][STREAM_SUB]),
OPT_TRACKCHOICE("secondary-sid", stream_id[1][STREAM_SUB]),
- OPT_TRACKCHOICE("ff-aid", stream_id_ff[STREAM_AUDIO]),
- OPT_TRACKCHOICE("ff-vid", stream_id_ff[STREAM_VIDEO]),
- OPT_TRACKCHOICE("ff-sid", stream_id_ff[STREAM_SUB]),
+ OPT_TRACKCHOICE("ff-aid", stream_id_ff[STREAM_AUDIO],
+ .deprecation_message = "no replacement"),
+ OPT_TRACKCHOICE("ff-vid", stream_id_ff[STREAM_VIDEO],
+ .deprecation_message = "no replacement"),
+ OPT_TRACKCHOICE("ff-sid", stream_id_ff[STREAM_SUB],
+ .deprecation_message = "no replacement"),
OPT_ALIAS("sub", "sid"),
OPT_ALIAS("video", "vid"),
OPT_ALIAS("audio", "aid"),
OPT_STRINGLIST("alang", stream_lang[STREAM_AUDIO], 0),
OPT_STRINGLIST("slang", stream_lang[STREAM_SUB], 0),
+ OPT_STRINGLIST("vlang", stream_lang[STREAM_VIDEO], 0),
OPT_FLAG("track-auto-selection", stream_auto_sel, 0),
OPT_STRING("lavfi-complex", lavfi_complex, UPDATE_LAVFI_COMPLEX),
@@ -418,8 +392,10 @@ const m_option_t mp_opts[] = {
// ------------------------- codec/vfilter options --------------------
+#if HAVE_LIBAF
OPT_SETTINGSLIST("af-defaults", af_defs, 0, &af_obj_list, ),
OPT_SETTINGSLIST("af", af_settings, 0, &af_obj_list, ),
+#endif
OPT_SETTINGSLIST("vf-defaults", vf_defs, 0, &vf_obj_list, ),
OPT_SETTINGSLIST("vf", vf_settings, 0, &vf_obj_list, ),
@@ -433,11 +409,14 @@ const m_option_t mp_opts[] = {
OPT_FLAG("ad-spdif-dtshd", dtshd, 0,
.deprecation_message = "use --audio-spdif instead"),
- OPT_CHOICE_C("hwdec", hwdec_api, 0, mp_hwdec_names),
+ OPT_STRING_VALIDATE("hwdec", hwdec_api, M_OPT_OPTIONAL_PARAM,
+ hwdec_validate_opt),
OPT_STRING("hwdec-codecs", hwdec_codecs, 0),
#if HAVE_VIDEOTOOLBOX_HWACCEL
- OPT_IMAGEFORMAT("videotoolbox-format", videotoolbox_format, 0, .min = -1),
+ OPT_IMAGEFORMAT("videotoolbox-format", videotoolbox_format, 0, .min = -1,
+ .deprecation_message = "use --hwdec-image-format instead"),
#endif
+ OPT_IMAGEFORMAT("hwdec-image-format", hwdec_image_format, 0, .min = -1),
// -1 means auto aspect (prefer container size until aspect change)
// 0 means square pixels
@@ -549,7 +528,6 @@ const m_option_t mp_opts[] = {
{"weak", -1})),
OPT_DOUBLE("audio-buffer", audio_buffer, M_OPT_MIN | M_OPT_MAX,
.min = 0, .max = 10),
- OPT_FLOATRANGE("balance", balance, 0, -1, 1),
OPT_STRING("title", wintitle, 0),
OPT_STRING("force-media-title", media_title, 0),
@@ -558,17 +536,14 @@ const m_option_t mp_opts[] = {
OPT_CHOICE_OR_INT("video-rotate", video_rotate, UPDATE_IMGPAR, 0, 359,
({"no", -1})),
OPT_CHOICE_C("video-stereo-mode", video_stereo_mode, UPDATE_IMGPAR,
- mp_stereo3d_names),
+ mp_stereo3d_names,
+ .deprecation_message = "mostly broken"),
OPT_CHOICE_OR_INT("cursor-autohide", cursor_autohide_delay, 0,
0, 30000, ({"no", -1}, {"always", -2})),
OPT_FLAG("cursor-autohide-fs-only", cursor_autohide_fs, 0),
OPT_FLAG("stop-screensaver", stop_screensaver, UPDATE_SCREENSAVER),
- OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0,
- .deprecation_message = "use Lua scripting instead"),
- OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
-
OPT_SUBSTRUCT("", video_equalizer, mp_csp_equalizer_conf, 0),
OPT_FLAG("use-filedir-conf", use_filedir_conf, 0),
@@ -686,8 +661,22 @@ const m_option_t mp_opts[] = {
OPT_SUBSTRUCT("", vo, vo_sub_opts, 0),
OPT_SUBSTRUCT("", demux_opts, demux_conf, 0),
-#if HAVE_GL
OPT_SUBSTRUCT("", gl_video_opts, gl_video_conf, 0),
+ OPT_SUBSTRUCT("", spirv_opts, spirv_conf, 0),
+
+#if HAVE_GL
+ OPT_SUBSTRUCT("", opengl_opts, opengl_conf, 0),
+#endif
+
+#if HAVE_VULKAN
+ OPT_SUBSTRUCT("", vulkan_opts, vulkan_conf, 0),
+#endif
+
+#if HAVE_D3D11
+ OPT_SUBSTRUCT("", d3d11_opts, d3d11_conf, 0),
+#if HAVE_D3D_HWACCEL
+ OPT_SUBSTRUCT("", d3d11va_opts, d3d11va_conf, 0),
+#endif
#endif
#if HAVE_EGL_ANGLE_WIN32
@@ -822,6 +811,7 @@ const m_option_t mp_opts[] = {
OPT_REPLACED("sub-ass-style-override", "sub-ass-override"),
OPT_REMOVED("fs-black-out-screens", NULL),
OPT_REPLACED("sub-paths", "sub-file-paths"),
+ OPT_REMOVED("heartbeat-cmd", "use Lua scripting instead"),
{0}
};
@@ -841,7 +831,6 @@ const struct MPOpts mp_default_opts = {
.audio_device = "auto",
.audio_client_name = "mpv",
.wintitle = "${?media-title:${media-title}}${!media-title:No file} - mpv",
- .heartbeat_interval = 30.0,
.stop_screensaver = 1,
.cursor_autohide_delay = 1000,
.video_osd = 1,
@@ -863,6 +852,7 @@ const struct MPOpts mp_default_opts = {
.lua_load_ytdl = 1,
.lua_ytdl_format = NULL,
.lua_ytdl_raw_options = NULL,
+ .lua_load_stats = 1,
#endif
.auto_load_scripts = 1,
.loop_times = 1,
@@ -929,7 +919,7 @@ const struct MPOpts mp_default_opts = {
.use_embedded_fonts = 1,
.screenshot_template = "mpv-shot%n",
- .hwdec_api = HAVE_RPI ? HWDEC_RPI : 0,
+ .hwdec_api = HAVE_RPI ? "mmal" : "no",
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video,vp9",
.videotoolbox_format = IMGFMT_NV12,
diff --git a/options/options.h b/options/options.h
index ecb38e3..9c9dd64 100644
--- a/options/options.h
+++ b/options/options.h
@@ -50,13 +50,10 @@ typedef struct mp_vo_opts {
char *mmcss_profile;
- // vo_wayland, vo_drm
+ // vo_drm
struct sws_opts *sws_opts;
- // vo_opengl, vo_opengl_cb
- char *gl_hwdec_interop;
// vo_drm
- char *drm_connector_spec;
- int drm_mode_id;
+ struct drm_opts *drm_opts;
} mp_vo_opts;
struct mp_cache_opts {
@@ -90,6 +87,7 @@ typedef struct MPOpts {
int lua_load_ytdl;
char *lua_ytdl_format;
char **lua_ytdl_raw_options;
+ int lua_load_stats;
int auto_load_scripts;
@@ -107,7 +105,6 @@ typedef struct MPOpts {
float rgain_preamp; // Set replaygain pre-amplification
int rgain_clip; // Enable/disable clipping prevention
float rgain_fallback;
- float balance;
int softvol_mute;
float softvol_max;
int gapless_audio;
@@ -181,8 +178,6 @@ typedef struct MPOpts {
char *status_msg;
char *osd_status_msg;
char *osd_msg[3];
- char *heartbeat_cmd;
- float heartbeat_interval;
int player_idle_mode;
int consolecontrols;
int playlist_pos;
@@ -290,9 +285,10 @@ typedef struct MPOpts {
int sub_clear_on_seek;
int teletext_page;
- int hwdec_api;
+ char *hwdec_api;
char *hwdec_codecs;
int videotoolbox_format;
+ int hwdec_image_format;
int w32_priority;
@@ -330,6 +326,11 @@ typedef struct MPOpts {
struct gl_video_opts *gl_video_opts;
struct angle_opts *angle_opts;
+ struct opengl_opts *opengl_opts;
+ struct vulkan_opts *vulkan_opts;
+ struct spirv_opts *spirv_opts;
+ struct d3d11_opts *d3d11_opts;
+ struct d3d11va_opts *d3d11va_opts;
struct cocoa_opts *cocoa_opts;
struct dvd_opts *dvd_opts;
@@ -348,4 +349,7 @@ extern const struct m_sub_options vo_sub_opts;
extern const struct m_sub_options stream_cache_conf;
extern const struct m_sub_options dvd_conf;
+int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param);
+
#endif
diff --git a/options/path.c b/options/path.c
index 5248a1d..dac9238 100644
--- a/options/path.c
+++ b/options/path.c
@@ -98,7 +98,7 @@ char *mp_find_user_config_file(void *talloc_ctx, struct mpv_global *global,
if (res)
res = mp_path_join(talloc_ctx, res, filename);
talloc_free(tmp);
- MP_VERBOSE(global, "config path: '%s' -> '%s'\n", filename, res ? res : "-");
+ MP_DBG(global, "config path: '%s' -> '%s'\n", filename, res ? res : "-");
return res;
}
@@ -119,12 +119,12 @@ static char **mp_find_all_config_files_limited(void *talloc_ctx,
char *file = mp_path_join_bstr(ret, bstr0(dir), fn);
if (mp_path_exists(file)) {
- MP_VERBOSE(global, "config path: '%.*s' -> '%s'\n",
- BSTR_P(fn), file);
+ MP_DBG(global, "config path: '%.*s' -> '%s'\n",
+ BSTR_P(fn), file);
MP_TARRAY_APPEND(NULL, ret, num_ret, file);
} else {
- MP_VERBOSE(global, "config path: '%.*s' -/-> '%s'\n",
- BSTR_P(fn), file);
+ MP_DBG(global, "config path: '%.*s' -/-> '%s'\n",
+ BSTR_P(fn), file);
}
}
}
@@ -189,7 +189,7 @@ char *mp_get_user_path(void *talloc_ctx, struct mpv_global *global,
}
if (!res)
res = talloc_strdup(talloc_ctx, path);
- MP_VERBOSE(global, "user path: '%s' -> '%s'\n", path, res);
+ MP_DBG(global, "user path: '%s' -> '%s'\n", path, res);
return res;
}
diff --git a/osdep/android/posix-spawn.c b/osdep/android/posix-spawn.c
new file mode 100644
index 0000000..a9bb27a
--- /dev/null
+++ b/osdep/android/posix-spawn.c
@@ -0,0 +1,72 @@
+/*
+ * posix-spawn replacement for Android
+ *
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include "osdep/android/posix-spawn.h"
+
+int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *fa, int fd, int newfd)
+{
+ if (fa->used >= MAX_FILE_ACTIONS)
+ return -1;
+ fa->action[fa->used].filedes = fd;
+ fa->action[fa->used].newfiledes = newfd;
+ fa->used++;
+ return 0;
+}
+
+int posix_spawn_file_actions_init(posix_spawn_file_actions_t *fa)
+{
+ fa->used = 0;
+ return 0;
+}
+
+int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *fa)
+{
+ return 0;
+}
+
+int posix_spawnp(pid_t *pid, const char *file,
+ const posix_spawn_file_actions_t *fa,
+ const posix_spawnattr_t *attrp,
+ char *const argv[], char *const envp[])
+{
+ pid_t p;
+
+ if (attrp != NULL)
+ return EINVAL;
+
+ p = fork();
+ if (p == -1)
+ return errno;
+
+ if (p == 0) {
+ for (int i = 0; i < fa->used; i++) {
+ int err = dup2(fa->action[i].filedes, fa->action[i].newfiledes);
+ if (err == -1)
+ goto fail;
+ }
+ execvpe(file, argv, envp);
+fail:
+ _exit(127);
+ }
+
+ *pid = p;
+ return 0;
+}
diff --git a/osdep/android/posix-spawn.h b/osdep/android/posix-spawn.h
new file mode 100644
index 0000000..d995b99
--- /dev/null
+++ b/osdep/android/posix-spawn.h
@@ -0,0 +1,43 @@
+/*
+ * posix-spawn replacement for Android
+ *
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#define MAX_FILE_ACTIONS 4
+
+typedef struct {
+ char dummy;
+} posix_spawnattr_t; /* unsupported */
+
+typedef struct {
+ int used;
+ struct {
+ int filedes, newfiledes;
+ } action[MAX_FILE_ACTIONS];
+} posix_spawn_file_actions_t;
+
+int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t*, int, int);
+int posix_spawn_file_actions_init(posix_spawn_file_actions_t*);
+int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t*);
+
+int posix_spawnp(pid_t*, const char*,
+ const posix_spawn_file_actions_t*, const posix_spawnattr_t *,
+ char *const [], char *const []);
diff --git a/osdep/atomic.h b/osdep/atomic.h
index 1d3e158..a5608fa 100644
--- a/osdep/atomic.h
+++ b/osdep/atomic.h
@@ -24,6 +24,7 @@
#if HAVE_STDATOMIC
#include <stdatomic.h>
+typedef _Atomic float mp_atomic_float;
#else
// Emulate the parts of C11 stdatomic.h needed by mpv.
@@ -36,6 +37,8 @@ typedef struct { long long v; } atomic_llong;
typedef struct { uint_least32_t v; } atomic_uint_least32_t;
typedef struct { unsigned long long v; } atomic_ullong;
+typedef struct { float v; } mp_atomic_float;
+
#define ATOMIC_VAR_INIT(x) \
{.v = (x)}
diff --git a/osdep/io.c b/osdep/io.c
index 78af30b..3b2061e 100644
--- a/osdep/io.c
+++ b/osdep/io.c
@@ -141,37 +141,154 @@ char *mp_to_utf8(void *talloc_ctx, const wchar_t *s)
#include <fcntl.h>
#include <pthread.h>
-static void copy_stat(struct mp_stat *dst, struct _stat64 *src)
+static void set_errno_from_lasterror(void)
+{
+ // This just handles the error codes expected from CreateFile at the moment
+ switch (GetLastError()) {
+ case ERROR_FILE_NOT_FOUND:
+ errno = ENOENT;
+ break;
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+ case ERROR_FILE_EXISTS:
+ case ERROR_ALREADY_EXISTS:
+ errno = EEXIST;
+ break;
+ case ERROR_PIPE_BUSY:
+ errno = EAGAIN;
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+}
+
+static time_t filetime_to_unix_time(int64_t wintime)
{
- dst->st_dev = src->st_dev;
- dst->st_ino = src->st_ino;
- dst->st_mode = src->st_mode;
- dst->st_nlink = src->st_nlink;
- dst->st_uid = src->st_uid;
- dst->st_gid = src->st_gid;
- dst->st_rdev = src->st_rdev;
- dst->st_size = src->st_size;
- dst->st_atime = src->st_atime;
- dst->st_mtime = src->st_mtime;
- dst->st_ctime = src->st_ctime;
+ static const int64_t hns_per_second = 10000000ll;
+ static const int64_t win_to_unix_epoch = 11644473600ll;
+ return wintime / hns_per_second - win_to_unix_epoch;
+}
+
+static bool get_file_ids_win8(HANDLE h, dev_t *dev, ino_t *ino)
+{
+ FILE_ID_INFO ii;
+ if (!GetFileInformationByHandleEx(h, FileIdInfo, &ii, sizeof(ii)))
+ return false;
+ *dev = ii.VolumeSerialNumber;
+ // The definition of FILE_ID_128 differs between mingw-w64 and the Windows
+ // SDK, but we can ignore that by just memcpying it. This will also
+ // truncate the file ID on 32-bit Windows, which doesn't support __int128.
+ // 128-bit file IDs are only used for ReFS, so that should be okay.
+ assert(sizeof(*ino) <= sizeof(ii.FileId));
+ memcpy(ino, &ii.FileId, sizeof(*ino));
+ return true;
+}
+
+#if HAVE_UWP
+static bool get_file_ids(HANDLE h, dev_t *dev, ino_t *ino)
+{
+ return false;
+}
+#else
+static bool get_file_ids(HANDLE h, dev_t *dev, ino_t *ino)
+{
+ // GetFileInformationByHandle works on FAT partitions and Windows 7, but
+ // doesn't work in UWP and can produce non-unique IDs on ReFS
+ BY_HANDLE_FILE_INFORMATION bhfi;
+ if (!GetFileInformationByHandle(h, &bhfi))
+ return false;
+ *dev = bhfi.dwVolumeSerialNumber;
+ *ino = ((ino_t)bhfi.nFileIndexHigh << 32) | bhfi.nFileIndexLow;
+ return true;
+}
+#endif
+
+// Like fstat(), but with a Windows HANDLE
+static int hstat(HANDLE h, struct mp_stat *buf)
+{
+ // Handle special (or unknown) file types first
+ switch (GetFileType(h) & ~FILE_TYPE_REMOTE) {
+ case FILE_TYPE_PIPE:
+ *buf = (struct mp_stat){ .st_nlink = 1, .st_mode = _S_IFIFO | 0644 };
+ return 0;
+ case FILE_TYPE_CHAR: // character device
+ *buf = (struct mp_stat){ .st_nlink = 1, .st_mode = _S_IFCHR | 0644 };
+ return 0;
+ case FILE_TYPE_UNKNOWN:
+ errno = EBADF;
+ return -1;
+ }
+
+ struct mp_stat st = { 0 };
+
+ FILE_BASIC_INFO bi;
+ if (!GetFileInformationByHandleEx(h, FileBasicInfo, &bi, sizeof(bi))) {
+ errno = EBADF;
+ return -1;
+ }
+ st.st_atime = filetime_to_unix_time(bi.LastAccessTime.QuadPart);
+ st.st_mtime = filetime_to_unix_time(bi.LastWriteTime.QuadPart);
+ st.st_ctime = filetime_to_unix_time(bi.ChangeTime.QuadPart);
+
+ FILE_STANDARD_INFO si;
+ if (!GetFileInformationByHandleEx(h, FileStandardInfo, &si, sizeof(si))) {
+ errno = EBADF;
+ return -1;
+ }
+ st.st_nlink = si.NumberOfLinks;
+
+ // Here we pretend Windows has POSIX permissions by pretending all
+ // directories are 755 and regular files are 644
+ if (si.Directory) {
+ st.st_mode |= _S_IFDIR | 0755;
+ } else {
+ st.st_mode |= _S_IFREG | 0644;
+ st.st_size = si.EndOfFile.QuadPart;
+ }
+
+ if (!get_file_ids_win8(h, &st.st_dev, &st.st_ino)) {
+ // Fall back to the Windows 7 method (also used for FAT in Win8)
+ if (!get_file_ids(h, &st.st_dev, &st.st_ino)) {
+ errno = EBADF;
+ return -1;
+ }
+ }
+
+ *buf = st;
+ return 0;
}
int mp_stat(const char *path, struct mp_stat *buf)
{
- struct _stat64 buf_;
wchar_t *wpath = mp_from_utf8(NULL, path);
- int res = _wstat64(wpath, &buf_);
+ HANDLE h = CreateFileW(wpath, FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | SECURITY_SQOS_PRESENT |
+ SECURITY_IDENTIFICATION, NULL);
talloc_free(wpath);
- copy_stat(buf, &buf_);
- return res;
+ if (h == INVALID_HANDLE_VALUE) {
+ set_errno_from_lasterror();
+ return -1;
+ }
+
+ int ret = hstat(h, buf);
+ CloseHandle(h);
+ return ret;
}
int mp_fstat(int fd, struct mp_stat *buf)
{
- struct _stat64 buf_;
- int res = _fstat64(fd, &buf_);
- copy_stat(buf, &buf_);
- return res;
+ HANDLE h = (HANDLE)_get_osfhandle(fd);
+ if (h == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ return -1;
+ }
+ // Use mpv's hstat() function rather than MSVCRT's fstat() because mpv's
+ // supports directories and device/inode numbers.
+ return hstat(h, buf);
}
#if HAVE_UWP
@@ -255,31 +372,161 @@ int mp_printf(const char *format, ...)
int mp_open(const char *filename, int oflag, ...)
{
- int mode = 0;
- if (oflag & _O_CREAT) {
- va_list va;
- va_start(va, oflag);
- mode = va_arg(va, int);
- va_end(va);
+ // Always use all share modes, which is useful for opening files that are
+ // open in other processes, and also more POSIX-like
+ static const DWORD share =
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ // Setting FILE_APPEND_DATA and avoiding GENERIC_WRITE/FILE_WRITE_DATA
+ // will make the file handle use atomic append behavior
+ static const DWORD append =
+ FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA;
+
+ DWORD access = 0;
+ DWORD disposition = 0;
+ DWORD flags = 0;
+
+ switch (oflag & (_O_RDONLY | _O_RDWR | _O_WRONLY | _O_APPEND)) {
+ case _O_RDONLY:
+ access = GENERIC_READ;
+ flags |= FILE_FLAG_BACKUP_SEMANTICS; // For opening directories
+ break;
+ case _O_RDWR:
+ access = GENERIC_READ | GENERIC_WRITE;
+ break;
+ case _O_RDWR | _O_APPEND:
+ case _O_RDONLY | _O_APPEND:
+ access = GENERIC_READ | append;
+ break;
+ case _O_WRONLY:
+ access = GENERIC_WRITE;
+ break;
+ case _O_WRONLY | _O_APPEND:
+ access = append;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
}
+
+ switch (oflag & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
+ case 0:
+ case _O_EXCL: // Like MSVCRT, ignore invalid use of _O_EXCL
+ disposition = OPEN_EXISTING;
+ break;
+ case _O_TRUNC:
+ case _O_TRUNC | _O_EXCL:
+ disposition = TRUNCATE_EXISTING;
+ break;
+ case _O_CREAT:
+ disposition = OPEN_ALWAYS;
+ flags |= FILE_ATTRIBUTE_NORMAL;
+ break;
+ case _O_CREAT | _O_TRUNC:
+ disposition = CREATE_ALWAYS;
+ break;
+ case _O_CREAT | _O_EXCL:
+ case _O_CREAT | _O_EXCL | _O_TRUNC:
+ disposition = CREATE_NEW;
+ flags |= FILE_ATTRIBUTE_NORMAL;
+ break;
+ }
+
+ // Opening a named pipe as a file can allow the pipe server to impersonate
+ // mpv's process, which could be a security issue. Set SQOS flags, so pipe
+ // servers can only identify the mpv process, not impersonate it.
+ if (disposition != CREATE_NEW)
+ flags |= SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION;
+
+ // Keep the same semantics for some MSVCRT-specific flags
+ if (oflag & _O_TEMPORARY) {
+ flags |= FILE_FLAG_DELETE_ON_CLOSE;
+ access |= DELETE;
+ }
+ if (oflag & _O_SHORT_LIVED)
+ flags |= FILE_ATTRIBUTE_TEMPORARY;
+ if (oflag & _O_SEQUENTIAL) {
+ flags |= FILE_FLAG_SEQUENTIAL_SCAN;
+ } else if (oflag & _O_RANDOM) {
+ flags |= FILE_FLAG_RANDOM_ACCESS;
+ }
+
+ // Open the Windows file handle
wchar_t *wpath = mp_from_utf8(NULL, filename);
- int res = _wopen(wpath, oflag, mode);
+ HANDLE h = CreateFileW(wpath, access, share, NULL, disposition, flags, NULL);
talloc_free(wpath);
- return res;
+ if (h == INVALID_HANDLE_VALUE) {
+ set_errno_from_lasterror();
+ return -1;
+ }
+
+ // Map the Windows file handle to a CRT file descriptor. Note: MSVCRT only
+ // cares about the following oflags.
+ oflag &= _O_APPEND | _O_RDONLY | _O_RDWR | _O_WRONLY;
+ oflag |= _O_NOINHERIT; // We never create inheritable handles
+ int fd = _open_osfhandle((intptr_t)h, oflag);
+ if (fd < 0) {
+ CloseHandle(h);
+ return -1;
+ }
+
+ return fd;
}
int mp_creat(const char *filename, int mode)
{
- return open(filename, O_CREAT|O_WRONLY|O_TRUNC, mode);
+ return mp_open(filename, _O_CREAT | _O_WRONLY | _O_TRUNC, mode);
}
FILE *mp_fopen(const char *filename, const char *mode)
{
- wchar_t *wpath = mp_from_utf8(NULL, filename);
- wchar_t *wmode = mp_from_utf8(wpath, mode);
- FILE *res = _wfopen(wpath, wmode);
- talloc_free(wpath);
- return res;
+ if (!mode[0]) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ int rwmode;
+ int oflags = 0;
+ switch (mode[0]) {
+ case 'r':
+ rwmode = _O_RDONLY;
+ break;
+ case 'w':
+ rwmode = _O_WRONLY;
+ oflags |= _O_CREAT | _O_TRUNC;
+ break;
+ case 'a':
+ rwmode = _O_WRONLY;
+ oflags |= _O_CREAT | _O_APPEND;
+ break;
+ default:
+ errno = EINVAL;
+ return NULL;
+ }
+
+ // Parse extra mode flags
+ for (const char *pos = mode + 1; *pos; pos++) {
+ switch (*pos) {
+ case '+': rwmode = _O_RDWR; break;
+ case 'x': oflags |= _O_EXCL; break;
+ // Ignore unknown flags (glibc does too)
+ default: break;
+ }
+ }
+
+ // Open a CRT file descriptor
+ int fd = mp_open(filename, rwmode | oflags);
+ if (fd < 0)
+ return NULL;
+
+ // Add 'b' to the mode so the CRT knows the file is opened in binary mode
+ char bmode[] = { mode[0], 'b', rwmode == _O_RDWR ? '+' : '\0', '\0' };
+ FILE *fp = fdopen(fd, bmode);
+ if (!fp) {
+ close(fd);
+ return NULL;
+ }
+
+ return fp;
}
// Windows' MAX_PATH/PATH_MAX/FILENAME_MAX is fixed to 260, but this limit
@@ -552,4 +799,18 @@ int msync(void *addr, size_t length, int flags)
}
#endif
+locale_t newlocale(int category, const char *locale, locale_t base)
+{
+ return (locale_t)1;
+}
+
+locale_t uselocale(locale_t locobj)
+{
+ return (locale_t)1;
+}
+
+void freelocale(locale_t locobj)
+{
+}
+
#endif // __MINGW32__
diff --git a/osdep/io.h b/osdep/io.h
index 35c7c52..e0d6284 100644
--- a/osdep/io.h
+++ b/osdep/io.h
@@ -27,11 +27,39 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <locale.h>
#if HAVE_GLOB_POSIX
#include <glob.h>
#endif
+#ifdef __ANDROID__
+# include <unistd.h>
+# include <stdio.h>
+
+// replace lseek with the 64bit variant
+#ifdef lseek
+# undef lseek
+#endif
+#define lseek(f,p,w) lseek64((f), (p), (w))
+
+// replace possible fseeko with a
+// lseek64 based solution.
+#ifdef fseeko
+# undef fseeko
+#endif
+static inline int mp_fseeko(FILE* fp, off64_t offset, int whence) {
+ int ret = -1;
+ if ((ret = fflush(fp)) != 0) {
+ return ret;
+ }
+
+ return lseek64(fileno(fp), offset, whence) >= 0 ? 0 : -1;
+}
+#define fseeko(f,p,w) mp_fseeko((f), (p), (w))
+
+#endif // __ANDROID__
+
#ifndef O_BINARY
#define O_BINARY 0
#endif
@@ -80,15 +108,25 @@ FILE *mp_tmpfile(void);
char *mp_getenv(const char *name);
off_t mp_lseek(int fd, off_t offset, int whence);
-// MinGW-w64 will define "stat" to something useless. Since this affects both
-// the type (struct stat) and the stat() function, it makes us harder to
-// override these separately.
-// Corresponds to struct _stat64 (copy & pasted, but using public types).
+// mp_stat types. MSVCRT's dev_t and ino_t are way too short to be unique.
+typedef uint64_t mp_dev_t_;
+#ifdef _WIN64
+typedef unsigned __int128 mp_ino_t_;
+#else
+// 32-bit Windows doesn't have a __int128-type, which means ReFS file IDs will
+// be truncated and might collide. This is probably not a problem because ReFS
+// is not available in consumer versions of Windows.
+typedef uint64_t mp_ino_t_;
+#endif
+#define dev_t mp_dev_t_
+#define ino_t mp_ino_t_
+
+// mp_stat uses a different structure to MSVCRT, with 64-bit inodes
struct mp_stat {
dev_t st_dev;
ino_t st_ino;
unsigned short st_mode;
- short st_nlink;
+ unsigned int st_nlink;
short st_uid;
short st_gid;
dev_t st_rdev;
@@ -155,6 +193,13 @@ int msync(void *addr, size_t length, int flags);
#define glob(...) mp_glob(__VA_ARGS__)
#define globfree(...) mp_globfree(__VA_ARGS__)
+// These are stubs since there is not anything that helps with this on Windows.
+#define locale_t int
+#define LC_ALL_MASK 0
+locale_t newlocale(int, const char *, locale_t);
+locale_t uselocale(locale_t);
+void freelocale(locale_t);
+
#else /* __MINGW32__ */
#include <sys/mman.h>
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index 0379491..de2514f 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -242,12 +242,35 @@ static void macosx_redirect_output_to_logfile(const char *filename)
[pool release];
}
-static bool bundle_started_from_finder()
+static bool bundle_started_from_finder(char **argv)
{
- NSDictionary *env = [[NSProcessInfo processInfo] environment];
- NSString *is_bundle = [env objectForKey:@"MPVBUNDLE"];
+ NSString *binary_path = [NSString stringWithUTF8String:argv[0]];
+ return [binary_path hasSuffix:@"mpv-bundle"];
+}
+
+static bool is_psn_argument(char *arg_to_check)
+{
+ NSString *arg = [NSString stringWithUTF8String:arg_to_check];
+ return [arg hasPrefix:@"-psn_"];
+}
- return is_bundle ? [is_bundle boolValue] : false;
+static void setup_bundle(int *argc, char *argv[])
+{
+ if (*argc > 1 && is_psn_argument(argv[1])) {
+ *argc = 1;
+ argv[1] = NULL;
+ }
+
+ NSDictionary *env = [[NSProcessInfo processInfo] environment];
+ NSString *path_bundle = [env objectForKey:@"PATH"];
+ NSString *path_new = [NSString stringWithFormat:@"%@:%@:%@:%@:%@",
+ path_bundle,
+ @"/usr/local/bin",
+ @"/usr/local/sbin",
+ @"/opt/local/bin",
+ @"/opt/local/sbin"];
+ setenv("PATH", [path_new UTF8String], 1);
+ setenv("MPVBUNDLE", "true", 1);
}
int cocoa_main(int argc, char *argv[])
@@ -260,7 +283,8 @@ int cocoa_main(int argc, char *argv[])
ctx.argc = &argc;
ctx.argv = &argv;
- if (bundle_started_from_finder()) {
+ if (bundle_started_from_finder(argv)) {
+ setup_bundle(&argc, argv);
macosx_redirect_output_to_logfile("mpv");
init_cocoa_application(true);
} else {
diff --git a/osdep/path-macosx.m b/osdep/path-macosx.m
index 73abb0d..8a5a704 100644
--- a/osdep/path-macosx.m
+++ b/osdep/path-macosx.m
@@ -21,7 +21,7 @@
const char *mp_get_platform_path_osx(void *talloc_ctx, const char *type)
{
- if (strcmp(type, "osxbundle") == 0) {
+ if (strcmp(type, "osxbundle") == 0 && getenv("MPVBUNDLE")) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *path = [[NSBundle mainBundle] resourcePath];
char *res = talloc_strdup(talloc_ctx, [path UTF8String]);
diff --git a/osdep/polldev.c b/osdep/polldev.c
new file mode 100644
index 0000000..ba2df2a
--- /dev/null
+++ b/osdep/polldev.c
@@ -0,0 +1,75 @@
+/*
+ * poll shim that supports device files on macOS.
+ *
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <poll.h>
+#include <sys/select.h>
+#include <stdio.h>
+
+#include "osdep/polldev.h"
+
+int polldev(struct pollfd fds[], nfds_t nfds, int timeout) {
+#ifdef __APPLE__
+ int maxfd = 0;
+ fd_set readfds, writefds, errorfds;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&errorfds);
+ for (size_t i = 0; i < nfds; ++i) {
+ struct pollfd *fd = &fds[i];
+ if (fd->fd > maxfd) {
+ maxfd = fd->fd;
+ }
+ if ((fd->events & POLLIN)) {
+ FD_SET(fd->fd, &readfds);
+ }
+ if ((fd->events & POLLOUT)) {
+ FD_SET(fd->fd, &writefds);
+ }
+ if ((fd->events & POLLERR)) {
+ FD_SET(fd->fd, &errorfds);
+ }
+ }
+ struct timeval _timeout = {
+ .tv_sec = timeout / 1000,
+ .tv_usec = (timeout % 1000) * 1000
+ };
+ int n = select(maxfd + 1, &readfds, &writefds, &errorfds,
+ timeout != -1 ? &_timeout : NULL);
+ if (n < 0) {
+ return n;
+ }
+ for (size_t i = 0; i < nfds; ++i) {
+ struct pollfd *fd = &fds[i];
+ fd->revents = 0;
+ if (FD_ISSET(fd->fd, &readfds)) {
+ fd->revents |= POLLIN;
+ }
+ if (FD_ISSET(fd->fd, &writefds)) {
+ fd->revents |= POLLOUT;
+ }
+ if (FD_ISSET(fd->fd, &errorfds)) {
+ fd->revents |= POLLERR;
+ }
+ }
+ return n;
+#else
+ return poll(fds, nfds, timeout);
+#endif
+}
diff --git a/osdep/polldev.h b/osdep/polldev.h
new file mode 100644
index 0000000..50b6db2
--- /dev/null
+++ b/osdep/polldev.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <poll.h>
+
+// Behaves like poll(3) but works for device files on macOS.
+// Only supports POLLIN, POLLOUT, and POLLERR.
+int polldev(struct pollfd fds[], nfds_t nfds, int timeout);
diff --git a/video/out/win32/exclusive_hack.h b/osdep/posix-spawn.h
index 883e215..d8bf874 100644
--- a/video/out/win32/exclusive_hack.h
+++ b/osdep/posix-spawn.h
@@ -1,4 +1,6 @@
/*
+ * posix-spawn wrapper
+ *
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
@@ -15,12 +17,11 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MP_WIN32_EXCLUSIVE_HACK_H_
-#define MP_WIN32_EXCLUSIVE_HACK_H_
-
-#include <stdbool.h>
-
-// Returns true if any program on the computer is in exclusive fullscreen mode
-bool mp_w32_is_in_exclusive_mode(void);
+#pragma once
+#ifdef __ANDROID__
+// posix_spawn(p) does not exist at all on Android
+#include "osdep/android/posix-spawn.h"
+#else
+#include <spawn.h>
#endif
diff --git a/osdep/subprocess-posix.c b/osdep/subprocess-posix.c
index c780473..a5fe43c 100644
--- a/osdep/subprocess-posix.c
+++ b/osdep/subprocess-posix.c
@@ -15,7 +15,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <spawn.h>
+#include "osdep/posix-spawn.h"
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
diff --git a/osdep/subprocess-win.c b/osdep/subprocess-win.c
index 5df09cd..bbb1f6f 100644
--- a/osdep/subprocess-win.c
+++ b/osdep/subprocess-win.c
@@ -251,7 +251,7 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
STARTUPINFOEXW si = {
.StartupInfo = {
.cb = sizeof(si),
- .dwFlags = STARTF_USESTDHANDLES,
+ .dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK,
.hStdInput = NULL,
.hStdOutput = pipes[0].write,
.hStdError = pipes[1].write,
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c
index eca6c69..1e90952 100644
--- a/osdep/terminal-unix.c
+++ b/osdep/terminal-unix.c
@@ -28,20 +28,12 @@
#include <pthread.h>
#include <assert.h>
-#if HAVE_TERMIOS
-#if HAVE_TERMIOS_H
#include <termios.h>
-#endif
-#if HAVE_SYS_TERMIOS_H
-#include <sys/termios.h>
-#endif
-#endif
-
#include <unistd.h>
-#include <poll.h>
#include "osdep/io.h"
#include "osdep/threads.h"
+#include "osdep/polldev.h"
#include "common/common.h"
#include "misc/bstr.h"
@@ -50,10 +42,10 @@
#include "misc/ctype.h"
#include "terminal.h"
-#if HAVE_TERMIOS
static volatile struct termios tio_orig;
static volatile int tio_orig_set;
-#endif
+
+static int tty_in = -1, tty_out = -1;
struct key_entry {
const char *seq;
@@ -181,7 +173,7 @@ static struct termbuf buf;
static bool getch2(struct input_ctx *input_ctx)
{
- int retval = read(0, &buf.b[buf.len], BUF_LEN - buf.len);
+ int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len);
/* Return false on EOF to stop running select() on the FD, as it'd
* trigger all the time. Note that it's possible to get temporary
* EOF on terminal if the user presses ctrl-d, but that shouldn't
@@ -269,9 +261,9 @@ static void enable_kx(bool enable)
// tty. Note that stderr being redirected away has no influence over mpv's
// I/O handling except for disabling the terminal OSD, and thus stderr
// shouldn't be relied on here either.
- if (isatty(STDOUT_FILENO)) {
+ if (isatty(tty_out)) {
char *cmd = enable ? "\033=" : "\033>";
- (void)write(STDOUT_FILENO, cmd, strlen(cmd));
+ (void)write(tty_out, cmd, strlen(cmd));
}
}
@@ -282,9 +274,8 @@ static void do_activate_getch2(void)
enable_kx(true);
-#if HAVE_TERMIOS
struct termios tio_new;
- tcgetattr(0,&tio_new);
+ tcgetattr(tty_in,&tio_new);
if (!tio_orig_set) {
tio_orig = tio_new;
@@ -294,8 +285,7 @@ static void do_activate_getch2(void)
tio_new.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tio_new.c_cc[VMIN] = 1;
tio_new.c_cc[VTIME] = 0;
- tcsetattr(0,TCSANOW,&tio_new);
-#endif
+ tcsetattr(tty_in,TCSANOW,&tio_new);
getch2_active = 1;
}
@@ -307,13 +297,11 @@ static void do_deactivate_getch2(void)
enable_kx(false);
-#if HAVE_TERMIOS
if (tio_orig_set) {
// once set, it will never be set again
// so we can cast away volatile here
- tcsetattr(0, TCSANOW, (const struct termios *) &tio_orig);
+ tcsetattr(tty_in, TCSANOW, (const struct termios *) &tio_orig);
}
-#endif
getch2_active = 0;
}
@@ -340,7 +328,7 @@ static void getch2_poll(void)
return;
// check if stdin is in the foreground process group
- int newstatus = (tcgetpgrp(0) == getpgrp());
+ int newstatus = (tcgetpgrp(tty_in) == getpgrp());
// and activate getch2 if it is, deactivate otherwise
if (newstatus)
@@ -379,6 +367,14 @@ static void close_death_pipe(void)
}
}
+static void close_tty(void)
+{
+ if (tty_in >= 0 && tty_in != STDIN_FILENO)
+ close(tty_in);
+
+ tty_in = tty_out = -1;
+}
+
static void quit_request_sighandler(int signum)
{
do_deactivate_getch2();
@@ -393,14 +389,14 @@ static void *terminal_thread(void *ptr)
while (1) {
getch2_poll();
struct pollfd fds[2] = {
- {.events = POLLIN, .fd = death_pipe[0]},
- {.events = POLLIN, .fd = STDIN_FILENO},
+ { .events = POLLIN, .fd = death_pipe[0] },
+ { .events = POLLIN, .fd = tty_in }
};
- poll(fds, stdin_ok ? 2 : 1, -1);
+ polldev(fds, stdin_ok ? 2 : 1, -1);
if (fds[0].revents)
break;
if (fds[1].revents)
- stdin_ok = getch2(input_ctx);
+ getch2(input_ctx);
}
char c;
bool quit = read(death_pipe[0], &c, 1) == 1 && c == 1;
@@ -424,13 +420,14 @@ void terminal_setup_getch(struct input_ctx *ictx)
// Disable reading from the terminal even if stdout is not a tty, to make
// mpv ... | less
// do the right thing.
- read_terminal = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
+ read_terminal = isatty(tty_in) && isatty(STDOUT_FILENO);
input_ctx = ictx;
if (pthread_create(&input_thread, NULL, terminal_thread, NULL)) {
input_ctx = NULL;
close_death_pipe();
+ close_tty();
return;
}
@@ -453,8 +450,6 @@ void terminal_uninit(void)
setsigaction(SIGTTIN, SIG_DFL, 0, false);
setsigaction(SIGTTOU, SIG_DFL, 0, false);
- do_deactivate_getch2();
-
if (input_ctx) {
(void)write(death_pipe[1], &(char){0}, 1);
pthread_join(input_thread, NULL);
@@ -462,6 +457,9 @@ void terminal_uninit(void)
input_ctx = NULL;
}
+ do_deactivate_getch2();
+ close_tty();
+
getch2_enabled = 0;
read_terminal = false;
}
@@ -474,7 +472,7 @@ bool terminal_in_background(void)
void terminal_get_size(int *w, int *h)
{
struct winsize ws;
- if (ioctl(0, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col)
+ if (ioctl(tty_in, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col)
return;
*w = ws.ws_col;
@@ -486,6 +484,12 @@ void terminal_init(void)
assert(!getch2_enabled);
getch2_enabled = 1;
+ tty_in = tty_out = open("/dev/tty", O_RDWR | O_CLOEXEC);
+ if (tty_in < 0) {
+ tty_in = STDIN_FILENO;
+ tty_out = STDOUT_FILENO;
+ }
+
// handlers to fix terminal settings
setsigaction(SIGCONT, continue_sighandler, 0, true);
setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false);
diff --git a/osdep/timer-linux.c b/osdep/timer-linux.c
index 1d52db2..6cd88a2 100644
--- a/osdep/timer-linux.c
+++ b/osdep/timer-linux.c
@@ -25,7 +25,6 @@
#include "config.h"
#include "timer.h"
-#if HAVE_NANOSLEEP
void mp_sleep_us(int64_t us)
{
if (us < 0)
@@ -35,14 +34,6 @@ void mp_sleep_us(int64_t us)
ts.tv_nsec = (us % 1000000) * 1000;
nanosleep(&ts, NULL);
}
-#else
-void mp_sleep_us(int64_t us)
-{
- if (us < 0)
- return;
- usleep(us);
-}
-#endif
#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(CLOCK_MONOTONIC)
uint64_t mp_raw_time_us(void)
diff --git a/player/audio.c b/player/audio.c
index 4c1ba90..965ac8f 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -14,7 +14,8 @@
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*
- * Parts under HAVE_GPL are licensed under GNU General Public License.
+ * Parts under HAVE_LIBAF are partially licensed under GNU General Public
+ * License (libaf/af.h glue code only).
*/
#include <stddef.h>
@@ -33,10 +34,10 @@
#include "common/common.h"
#include "osdep/timer.h"
-#include "audio/audio.h"
#include "audio/audio_buffer.h"
+#include "audio/aconverter.h"
+#include "audio/format.h"
#include "audio/decode/dec_audio.h"
-#include "audio/filter/af.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "video/decode/dec_video.h"
@@ -54,6 +55,11 @@ enum {
AD_STARVE = -6,
};
+#if HAVE_LIBAF
+
+#include "audio/audio.h"
+#include "audio/filter/af.h"
+
// Use pitch correction only for speed adjustments by the user, not minor sync
// correction ones.
static int get_speed_method(struct MPContext *mpctx)
@@ -122,6 +128,57 @@ fail:
mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
}
+static int recreate_audio_filters(struct MPContext *mpctx)
+{
+ assert(mpctx->ao_chain);
+
+ struct af_stream *afs = mpctx->ao_chain->af;
+ if (afs->initialized < 1 && af_init(afs) < 0)
+ goto fail;
+
+ recreate_speed_filters(mpctx);
+ if (afs->initialized < 1 && af_init(afs) < 0)
+ goto fail;
+
+ if (mpctx->opts->softvol == SOFTVOL_NO)
+ MP_ERR(mpctx, "--softvol=no is not supported anymore.\n");
+
+ mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
+
+ return 0;
+
+fail:
+ MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
+ return -1;
+}
+
+int reinit_audio_filters(struct MPContext *mpctx)
+{
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ if (!ao_c)
+ return 0;
+
+ double delay = 0;
+ if (ao_c->af->initialized > 0)
+ delay = af_calc_delay(ao_c->af);
+
+ af_uninit(ao_c->af);
+ if (recreate_audio_filters(mpctx) < 0)
+ return -1;
+
+ // Only force refresh if the amount of dropped buffered data is going to
+ // cause "issues" for the A/V sync logic.
+ if (mpctx->audio_status == STATUS_PLAYING && delay > 0.2)
+ issue_refresh_seek(mpctx, MPSEEK_EXACT);
+ return 1;
+}
+
+#else /* HAVE_LIBAV */
+
+int reinit_audio_filters(struct MPContext *mpctx) { return 0; }
+
+#endif /* else HAVE_LIBAF */
+
static double db_gain(double db)
{
return pow(10.0, db/20.0);
@@ -130,11 +187,13 @@ static double db_gain(double db)
static float compute_replaygain(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
- struct ao_chain *ao_c = mpctx->ao_chain;
float rgain = 1.0;
- struct replaygain_data *rg = ao_c->af->replaygain_data;
+ struct replaygain_data *rg = NULL;
+ struct track *track = mpctx->current_track[0][STREAM_AUDIO];
+ if (track)
+ rg = track->stream->codec->replaygain_data;
if (opts->rgain_mode && rg) {
MP_VERBOSE(mpctx, "Replaygain: Track=%f/%f Album=%f/%f\n",
rg->track_gain, rg->track_peak,
@@ -171,7 +230,7 @@ void audio_update_volume(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct ao_chain *ao_c = mpctx->ao_chain;
- if (!ao_c || ao_c->af->initialized < 1)
+ if (!ao_c || !ao_c->ao)
return;
float gain = MPMAX(opts->softvol_volume / 100.0, 0);
@@ -180,108 +239,7 @@ void audio_update_volume(struct MPContext *mpctx)
if (opts->softvol_mute == 1)
gain = 0.0;
-#if HAVE_GPL
- if (!af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)) {
- if (gain == 1.0)
- return;
- MP_VERBOSE(mpctx, "Inserting volume filter.\n");
- char *args[] = {"warn", "no", NULL};
- if (!(af_add(ao_c->af, "volume", "softvol", args)
- && af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)))
- MP_ERR(mpctx, "No volume control available.\n");
- }
-#endif
-}
-
-/* NOTE: Currently the balance code is seriously buggy: it always changes
- * the af_pan mapping between the first two input channels and first two
- * output channels to particular values. These values make sense for an
- * af_pan instance that was automatically inserted for balance control
- * only and is otherwise an identity transform, but if the filter was
- * there for another reason, then ignoring and overriding the original
- * values is completely wrong.
- */
-void audio_update_balance(struct MPContext *mpctx)
-{
- struct MPOpts *opts = mpctx->opts;
- struct ao_chain *ao_c = mpctx->ao_chain;
- if (!ao_c || ao_c->af->initialized < 1)
- return;
-
- float val = opts->balance;
-
- if (af_control_any_rev(ao_c->af, AF_CONTROL_SET_PAN_BALANCE, &val))
- return;
-
- if (val == 0)
- return;
-
- struct af_instance *af_pan_balance;
- if (!(af_pan_balance = af_add(ao_c->af, "pan", "autopan", NULL))) {
- MP_ERR(mpctx, "No balance control available.\n");
- return;
- }
-
- /* make all other channels pass through since by default pan blocks all */
- for (int i = 2; i < AF_NCH; i++) {
- float level[AF_NCH] = {0};
- level[i] = 1.f;
- af_control_ext_t arg_ext = { .ch = i, .arg = level };
- af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
- &arg_ext);
- }
-
- af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
-}
-
-static int recreate_audio_filters(struct MPContext *mpctx)
-{
- assert(mpctx->ao_chain);
-
-#if HAVE_GPL
- struct af_stream *afs = mpctx->ao_chain->af;
- if (afs->initialized < 1 && af_init(afs) < 0)
- goto fail;
-
- recreate_speed_filters(mpctx);
- if (afs->initialized < 1 && af_init(afs) < 0)
- goto fail;
-#endif
-
- if (mpctx->opts->softvol == SOFTVOL_NO)
- MP_ERR(mpctx, "--softvol=no is not supported anymore.\n");
-
- audio_update_volume(mpctx);
- audio_update_balance(mpctx);
-
- mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
-
- return 0;
-
-fail:
- MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
- return -1;
-}
-
-int reinit_audio_filters(struct MPContext *mpctx)
-{
- struct ao_chain *ao_c = mpctx->ao_chain;
- if (!ao_c)
- return 0;
-
- double delay = 0;
- if (ao_c->af->initialized > 0)
- delay = af_calc_delay(ao_c->af);
-
- af_uninit(ao_c->af);
- if (recreate_audio_filters(mpctx) < 0)
- return -1;
-
- // Only force refresh if the amount of dropped buffered data is going to
- // cause "issues" for the A/V sync logic.
- if (mpctx->audio_status == STATUS_PLAYING && delay > 0.2)
- issue_refresh_seek(mpctx, MPSEEK_EXACT);
- return 1;
+ ao_set_gain(ao_c->ao, gain);
}
// Call this if opts->playback_speed or mpctx->speed_factor_* change.
@@ -290,20 +248,26 @@ void update_playback_speed(struct MPContext *mpctx)
mpctx->audio_speed = mpctx->opts->playback_speed * mpctx->speed_factor_a;
mpctx->video_speed = mpctx->opts->playback_speed * mpctx->speed_factor_v;
+#if HAVE_LIBAF
if (!mpctx->ao_chain || mpctx->ao_chain->af->initialized < 1)
return;
if (!update_speed_filters(mpctx))
recreate_audio_filters(mpctx);
+#endif
}
static void ao_chain_reset_state(struct ao_chain *ao_c)
{
ao_c->pts = MP_NOPTS_VALUE;
ao_c->pts_reset = false;
- talloc_free(ao_c->input_frame);
- ao_c->input_frame = NULL;
+ TA_FREEP(&ao_c->input_frame);
+ TA_FREEP(&ao_c->output_frame);
+#if HAVE_LIBAF
af_seek_reset(ao_c->af);
+#endif
+ if (ao_c->conv)
+ mp_aconverter_flush(ao_c->conv);
mp_audio_buffer_clear(ao_c->ao_buffer);
if (ao_c->audio_src)
@@ -350,9 +314,14 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
if (ao_c->filter_src)
lavfi_set_connected(ao_c->filter_src, false);
+#if HAVE_LIBAF
af_destroy(ao_c->af);
+#endif
+ talloc_free(ao_c->conv);
talloc_free(ao_c->input_frame);
talloc_free(ao_c->input_format);
+ talloc_free(ao_c->output_frame);
+ talloc_free(ao_c->filter_input_format);
talloc_free(ao_c->ao_buffer);
talloc_free(ao_c);
}
@@ -369,16 +338,17 @@ void uninit_audio_chain(struct MPContext *mpctx)
}
}
-static void get_ao_format(struct ao *ao, struct mp_audio *aformat)
+static char *audio_config_to_str_buf(char *buf, size_t buf_sz, int rate,
+ int format, struct mp_chmap channels)
{
- int samplerate;
- int format;
- struct mp_chmap channels;
- ao_get_format(ao, &samplerate, &format, &channels);
- *aformat = (struct mp_audio){0};
- mp_audio_set_format(aformat, format);
- mp_audio_set_channels(aformat, &channels);
- aformat->rate = samplerate;
+ char ch[128];
+ mp_chmap_to_str_buf(ch, sizeof(ch), &channels);
+ char *hr_ch = mp_chmap_to_str_hr(&channels);
+ if (strcmp(hr_ch, ch) != 0)
+ mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
+ snprintf(buf, buf_sz, "%dHz %s %dch %s", rate,
+ ch, channels.num, af_fmt_to_str(format));
+ return buf;
}
static void reinit_audio_filters_and_output(struct MPContext *mpctx)
@@ -387,7 +357,6 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
struct ao_chain *ao_c = mpctx->ao_chain;
assert(ao_c);
struct track *track = ao_c->track;
- struct af_stream *afs = ao_c->af;
if (!mp_aframe_config_is_valid(ao_c->input_format)) {
// We don't know the audio format yet - so configure it later as we're
@@ -403,24 +372,33 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
uninit_audio_out(mpctx);
}
+ TA_FREEP(&ao_c->output_frame);
+
+ int out_rate = 0;
+ int out_format = 0;
+ struct mp_chmap out_channels = {0};
+ if (mpctx->ao) {
+ ao_get_format(mpctx->ao, &out_rate, &out_format, &out_channels);
+ } else if (af_fmt_is_pcm(mp_aframe_get_format(ao_c->input_format))) {
+ out_rate = opts->force_srate;
+ out_format = opts->audio_output_format;
+ if (opts->audio_output_channels.num_chmaps == 1)
+ out_channels = opts->audio_output_channels.chmaps[0];
+ }
+
+#if HAVE_LIBAF
+ struct af_stream *afs = ao_c->af;
+
struct mp_audio in_format;
mp_audio_config_from_aframe(&in_format, ao_c->input_format);
if (mpctx->ao && mp_audio_config_equals(&in_format, &afs->input))
return;
afs->output = (struct mp_audio){0};
- if (mpctx->ao) {
- get_ao_format(mpctx->ao, &afs->output);
- } else if (af_fmt_is_pcm(mp_aframe_get_format(ao_c->input_format))) {
- afs->output.rate = opts->force_srate;
- mp_audio_set_format(&afs->output, opts->audio_output_format);
- if (opts->audio_output_channels.num_chmaps == 1) {
- mp_audio_set_channels(&afs->output,
- &opts->audio_output_channels.chmaps[0]);
- }
- }
+ afs->output.rate = out_rate;
+ mp_audio_set_format(&afs->output, out_format);
+ mp_audio_set_channels(&afs->output, &out_channels);
-#if HAVE_GPL
// filter input format: same as codec's output format:
afs->input = in_format;
@@ -432,9 +410,27 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
goto init_error;
}
+ out_rate = afs->output.rate;
+ out_format = afs->output.format;
+ out_channels = afs->output.channels;
+#else
+ if (mpctx->ao && ao_c->filter_input_format &&
+ mp_aframe_config_equals(ao_c->filter_input_format, ao_c->input_format))
+ return;
+
+ TA_FREEP(&ao_c->filter_input_format);
+
+ if (!out_rate)
+ out_rate = mp_aframe_get_rate(ao_c->input_format);
+ if (!out_format)
+ out_format = mp_aframe_get_format(ao_c->input_format);
+ if (!out_channels.num)
+ mp_aframe_get_chmap(ao_c->input_format, &out_channels);
+#endif
+
if (!mpctx->ao) {
int ao_flags = 0;
- bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
+ bool spdif_fallback = af_fmt_is_spdif(out_format) &&
ao_c->spdif_passthrough;
if (opts->ao_null_fallback && !spdif_fallback)
@@ -446,30 +442,32 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
if (opts->audio_exclusive)
ao_flags |= AO_INIT_EXCLUSIVE;
- if (af_fmt_is_pcm(afs->output.format)) {
+ if (af_fmt_is_pcm(out_format)) {
if (!opts->audio_output_channels.set ||
opts->audio_output_channels.auto_safe)
ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
- mp_chmap_sel_list(&afs->output.channels,
+ mp_chmap_sel_list(&out_channels,
opts->audio_output_channels.chmaps,
opts->audio_output_channels.num_chmaps);
}
- mp_audio_set_channels(&afs->output, &afs->output.channels);
-
mpctx->ao = ao_init_best(mpctx->global, ao_flags, mp_wakeup_core_cb,
- mpctx, mpctx->encode_lavc_ctx, afs->output.rate,
- afs->output.format, afs->output.channels);
+ mpctx, mpctx->encode_lavc_ctx, out_rate,
+ out_format, out_channels);
ao_c->ao = mpctx->ao;
- struct mp_audio fmt = {0};
+ int ao_rate = 0;
+ int ao_format = 0;
+ struct mp_chmap ao_channels = {0};
if (mpctx->ao)
- get_ao_format(mpctx->ao, &fmt);
+ ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
// Verify passthrough format was not changed.
- if (mpctx->ao && af_fmt_is_spdif(afs->output.format)) {
- if (!mp_audio_config_equals(&afs->output, &fmt)) {
+ if (mpctx->ao && af_fmt_is_spdif(out_format)) {
+ if (out_rate != ao_rate || out_format != ao_format ||
+ !mp_chmap_equals(&out_channels, &ao_channels))
+ {
MP_ERR(mpctx, "Passthrough format unsupported.\n");
ao_uninit(mpctx->ao);
mpctx->ao = NULL;
@@ -497,16 +495,36 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
goto init_error;
}
- mp_audio_buffer_reinit(ao_c->ao_buffer, &fmt);
- afs->output = fmt;
+ mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, ao_format, &ao_channels,
+ ao_rate);
+
+#if HAVE_LIBAF
+ afs->output = (struct mp_audio){0};
+ afs->output.rate = ao_rate;
+ mp_audio_set_format(&afs->output, ao_format);
+ mp_audio_set_channels(&afs->output, &ao_channels);
if (!mp_audio_config_equals(&afs->output, &afs->filter_output))
afs->initialized = 0;
+#else
+ int in_rate = mp_aframe_get_rate(ao_c->input_format);
+ int in_format = mp_aframe_get_format(ao_c->input_format);
+ struct mp_chmap in_chmap = {0};
+ mp_aframe_get_chmap(ao_c->input_format, &in_chmap);
+ if (!mp_aconverter_reconfig(ao_c->conv, in_rate, in_format, in_chmap,
+ ao_rate, ao_format, ao_channels))
+ {
+ MP_ERR(mpctx, "Cannot convert audio data for output.\n");
+ goto init_error;
+ }
+ ao_c->filter_input_format = mp_aframe_new_ref(ao_c->input_format);
+#endif
- mpctx->ao_decoder_fmt = mp_aframe_create();
- mp_aframe_config_copy(mpctx->ao_decoder_fmt, ao_c->input_format);
+ mpctx->ao_decoder_fmt = mp_aframe_new_ref(ao_c->input_format);
+ char tmp[80];
MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
- mp_audio_config_to_str(&fmt));
+ audio_config_to_str_buf(tmp, sizeof(tmp), ao_rate, ao_format,
+ ao_channels));
MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
update_window_title(mpctx, true);
@@ -514,11 +532,13 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
}
+#if HAVE_LIBAF
if (recreate_audio_filters(mpctx) < 0)
goto init_error;
#endif
update_playback_speed(mpctx);
+ audio_update_volume(mpctx);
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
@@ -583,9 +603,11 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
struct ao_chain *ao_c = talloc_zero(NULL, struct ao_chain);
mpctx->ao_chain = ao_c;
ao_c->log = mpctx->log;
+#if HAVE_LIBAF
ao_c->af = af_new(mpctx->global);
- if (track && track->stream)
- ao_c->af->replaygain_data = track->stream->codec->replaygain_data;
+#else
+ ao_c->conv = mp_aconverter_create(mpctx->global, mpctx->log, NULL);
+#endif
ao_c->spdif_passthrough = true;
ao_c->pts = MP_NOPTS_VALUE;
ao_c->ao_buffer = mp_audio_buffer_create(NULL);
@@ -603,9 +625,13 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
reset_audio_state(mpctx);
if (mpctx->ao) {
- struct mp_audio fmt;
- get_ao_format(mpctx->ao, &fmt);
- mp_audio_buffer_reinit(ao_c->ao_buffer, &fmt);
+ int rate;
+ int format;
+ struct mp_chmap channels;
+ ao_get_format(mpctx->ao, &rate, &format, &channels);
+ mp_audio_buffer_reinit_fmt(ao_c->ao_buffer, format, &channels, rate);
+
+ audio_update_volume(mpctx);
}
mp_wakeup_core(mpctx);
@@ -625,16 +651,26 @@ double written_audio_pts(struct MPContext *mpctx)
if (!ao_c)
return MP_NOPTS_VALUE;
- if (ao_c->af->initialized < 1)
- return MP_NOPTS_VALUE;
-
// first calculate the end pts of audio that has been output by decoder
double a_pts = ao_c->pts;
if (a_pts == MP_NOPTS_VALUE)
return MP_NOPTS_VALUE;
// Data buffered in audio filters, measured in seconds of "missing" output
- double buffered_output = af_calc_delay(ao_c->af);
+ double buffered_output = 0;
+
+#if HAVE_LIBAF
+ if (ao_c->af->initialized < 1)
+ return MP_NOPTS_VALUE;
+
+ buffered_output += af_calc_delay(ao_c->af);
+#endif
+
+ if (ao_c->conv)
+ buffered_output += mp_aconverter_get_latency(ao_c->conv);
+
+ if (ao_c->output_frame)
+ buffered_output += mp_aframe_duration(ao_c->output_frame);
// Data that was ready for ao but was buffered because ao didn't fully
// accept everything to internal buffers yet
@@ -656,25 +692,28 @@ double playing_audio_pts(struct MPContext *mpctx)
return pts - mpctx->audio_speed * ao_get_delay(mpctx->ao);
}
-static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags)
+static int write_to_ao(struct MPContext *mpctx, uint8_t **planes, int samples,
+ int flags)
{
if (mpctx->paused)
return 0;
struct ao *ao = mpctx->ao;
- struct mp_audio out_format;
- get_ao_format(ao, &out_format);
+ int samplerate;
+ int format;
+ struct mp_chmap channels;
+ ao_get_format(ao, &samplerate, &format, &channels);
#if HAVE_ENCODING
encode_lavc_set_audio_pts(mpctx->encode_lavc_ctx, playing_audio_pts(mpctx));
#endif
- if (data->samples == 0)
+ if (samples == 0)
return 0;
- double real_samplerate = out_format.rate / mpctx->audio_speed;
- int played = ao_play(mpctx->ao, data->planes, data->samples, flags);
- assert(played <= data->samples);
+ double real_samplerate = samplerate / mpctx->audio_speed;
+ int played = ao_play(mpctx->ao, (void **)planes, samples, flags);
+ assert(played <= samples);
if (played > 0) {
mpctx->shown_aframes += played;
mpctx->delay += played / real_samplerate;
- mpctx->written_audio += played / (double)out_format.rate;
+ mpctx->written_audio += played / (double)samplerate;
return played;
}
return 0;
@@ -714,9 +753,12 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
if (mpctx->audio_status != STATUS_SYNCING)
return true;
- struct mp_audio out_format = {0};
- get_ao_format(mpctx->ao, &out_format);
- double play_samplerate = out_format.rate / mpctx->audio_speed;
+ int ao_rate;
+ int ao_format;
+ struct mp_chmap ao_channels;
+ ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
+
+ double play_samplerate = ao_rate / mpctx->audio_speed;
if (!opts->initial_audio_sync) {
mpctx->audio_status = STATUS_FILLING;
@@ -778,25 +820,27 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
}
mpctx->audio_allow_second_chance_seek = false;
- int align = af_format_sample_alignment(out_format.format);
+ int align = af_format_sample_alignment(ao_format);
*skip = (int)(-ptsdiff * play_samplerate) / align * align;
return true;
}
-static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
+static bool copy_output(struct MPContext *mpctx, struct ao_chain *ao_c,
int minsamples, double endpts, bool eof, bool *seteof)
{
- struct af_stream *afs = mpctx->ao_chain->af;
+ struct mp_audio_buffer *outbuf = ao_c->ao_buffer;
- while (mp_audio_buffer_samples(outbuf) < minsamples) {
- if (af_output_frame(afs, eof) < 0)
- return true; // error, stop doing stuff
+ int ao_rate;
+ int ao_format;
+ struct mp_chmap ao_channels;
+ ao_get_format(ao_c->ao, &ao_rate, &ao_format, &ao_channels);
+ while (mp_audio_buffer_samples(outbuf) < minsamples) {
int cursamples = mp_audio_buffer_samples(outbuf);
int maxsamples = INT_MAX;
if (endpts != MP_NOPTS_VALUE) {
- double rate = afs->output.rate / mpctx->audio_speed;
+ double rate = ao_rate / mpctx->audio_speed;
double curpts = written_audio_pts(mpctx);
if (curpts != MP_NOPTS_VALUE) {
double remaining =
@@ -805,24 +849,43 @@ static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
}
}
- struct mp_audio *mpa = af_read_output_frame(afs);
- if (!mpa)
+ if (!ao_c->output_frame || !mp_aframe_get_size(ao_c->output_frame)) {
+ TA_FREEP(&ao_c->output_frame);
+#if HAVE_LIBAF
+ struct af_stream *afs = mpctx->ao_chain->af;
+ if (af_output_frame(afs, eof) < 0)
+ return true; // error, stop doing stuff
+ struct mp_audio *mpa = af_read_output_frame(afs);
+ ao_c->output_frame = mp_audio_to_aframe(mpa);
+ talloc_free(mpa);
+#else
+ if (eof)
+ mp_aconverter_write_input(ao_c->conv, NULL);
+ mp_aconverter_set_speed(ao_c->conv, mpctx->audio_speed);
+ bool got_eof;
+ ao_c->output_frame = mp_aconverter_read_output(ao_c->conv, &got_eof);
+#endif
+ }
+
+ if (!ao_c->output_frame)
return false; // out of data
- if (cursamples + mpa->samples > maxsamples) {
+ if (cursamples + mp_aframe_get_size(ao_c->output_frame) > maxsamples) {
if (cursamples < maxsamples) {
- struct mp_audio pre = *mpa;
- pre.samples = maxsamples - cursamples;
- mp_audio_buffer_append(outbuf, &pre);
- mp_audio_skip_samples(mpa, pre.samples);
+ uint8_t **data = mp_aframe_get_data_ro(ao_c->output_frame);
+ mp_audio_buffer_append(outbuf, (void **)data,
+ maxsamples - cursamples);
+ mp_aframe_skip_samples(ao_c->output_frame,
+ maxsamples - cursamples);
}
- af_unread_output_frame(afs, mpa);
*seteof = true;
return true;
}
- mp_audio_buffer_append(outbuf, mpa);
- talloc_free(mpa);
+ uint8_t **data = mp_aframe_get_data_ro(ao_c->output_frame);
+ mp_audio_buffer_append(outbuf, (void **)data,
+ mp_aframe_get_size(ao_c->output_frame));
+ TA_FREEP(&ao_c->output_frame);
}
return true;
}
@@ -853,7 +916,6 @@ static int decode_new_frame(struct ao_chain *ao_c)
}
}
-#if HAVE_GPL
/* Try to get at least minsamples decoded+filtered samples in outbuf
* (total length including possible existing data).
* Return 0 on success, or negative AD_* error code.
@@ -863,9 +925,14 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
int minsamples)
{
struct ao_chain *ao_c = mpctx->ao_chain;
+#if HAVE_LIBAF
struct af_stream *afs = ao_c->af;
if (afs->initialized < 1)
return AD_ERR;
+#else
+ if (!ao_c->filter_input_format)
+ return AD_ERR;
+#endif
MP_STATS(ao_c, "start audio");
@@ -876,7 +943,7 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
while (1) {
res = 0;
- if (copy_output(mpctx, outbuf, minsamples, endpts, false, &eof))
+ if (copy_output(mpctx, ao_c, minsamples, endpts, false, &eof))
break;
res = decode_new_frame(ao_c);
@@ -886,41 +953,58 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
break;
if (res < 0) {
// drain filters first (especially for true EOF case)
- copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
+ copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
break;
}
// On format change, make sure to drain the filter chain.
+#if HAVE_LIBAF
struct mp_audio in_format;
mp_audio_config_from_aframe(&in_format, ao_c->input_format);
if (!mp_audio_config_equals(&afs->input, &in_format)) {
- copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
+ copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
res = AD_NEW_FMT;
break;
}
+#else
+ if (!mp_aframe_config_equals(ao_c->filter_input_format,
+ ao_c->input_format))
+ {
+ copy_output(mpctx, ao_c, minsamples, endpts, true, &eof);
+ res = AD_NEW_FMT;
+ break;
+ }
+#endif
- struct mp_audio *mpa = mp_audio_from_aframe(ao_c->input_frame);
- talloc_free(ao_c->input_frame);
- ao_c->input_frame = NULL;
- if (!mpa)
- abort();
- if (mpa->pts == MP_NOPTS_VALUE) {
+ double pts = mp_aframe_get_pts(ao_c->input_frame);
+ if (pts == MP_NOPTS_VALUE) {
ao_c->pts = MP_NOPTS_VALUE;
} else {
// Attempt to detect jumps in PTS. Even for the lowest sample rates
// and with worst container rounded timestamp, this should be a
// margin more than enough.
- double desync = mpa->pts - ao_c->pts;
+ double desync = pts - ao_c->pts;
if (ao_c->pts != MP_NOPTS_VALUE && fabs(desync) > 0.1) {
MP_WARN(ao_c, "Invalid audio PTS: %f -> %f\n",
- ao_c->pts, mpa->pts);
+ ao_c->pts, pts);
if (desync >= 5)
ao_c->pts_reset = true;
}
- ao_c->pts = mpa->pts + mpa->samples / (double)mpa->rate;
+ ao_c->pts = mp_aframe_end_pts(ao_c->input_frame);
}
+
+#if HAVE_LIBAF
+ struct mp_audio *mpa = mp_audio_from_aframe(ao_c->input_frame);
+ talloc_free(ao_c->input_frame);
+ ao_c->input_frame = NULL;
+ if (!mpa)
+ abort();
if (af_filter_frame(afs, mpa) < 0)
return AD_ERR;
+#else
+ if (mp_aconverter_write_input(ao_c->conv, ao_c->input_frame))
+ ao_c->input_frame = NULL;
+#endif
}
if (res == 0 && mp_audio_buffer_samples(outbuf) < minsamples && eof)
@@ -930,7 +1014,6 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
return res;
}
-#endif
void reload_audio_output(struct MPContext *mpctx)
{
@@ -951,7 +1034,10 @@ void reload_audio_output(struct MPContext *mpctx)
ao_c->spdif_passthrough = true;
ao_c->spdif_failed = false;
d_audio->try_spdif = true;
+#if HAVE_LIBAF
ao_c->af->initialized = 0;
+#endif
+ TA_FREEP(&ao_c->filter_input_format);
if (!audio_init_best_codec(d_audio)) {
MP_ERR(mpctx, "Error reinitializing audio.\n");
error_on_track(mpctx, ao_c->track);
@@ -976,7 +1062,12 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (!ao_c)
return;
- if (ao_c->af->initialized < 1 || !mpctx->ao) {
+ bool is_initialized = !!ao_c->filter_input_format;
+#if HAVE_LIBAF
+ is_initialized = ao_c->af->initialized == 1;
+#endif
+
+ if (!is_initialized || !mpctx->ao) {
// Probe the initial audio format. Returns AD_OK (and does nothing) if
// the format is already known.
int r = AD_NO_PROGRESS;
@@ -1006,10 +1097,12 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
return;
}
- struct mp_audio out_format = {0};
- get_ao_format(mpctx->ao, &out_format);
- double play_samplerate = out_format.rate / mpctx->audio_speed;
- int align = af_format_sample_alignment(out_format.format);
+ int ao_rate;
+ int ao_format;
+ struct mp_chmap ao_channels;
+ ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
+ double play_samplerate = ao_rate / mpctx->audio_speed;
+ int align = af_format_sample_alignment(ao_format);
// If audio is infinitely fast, somehow try keeping approximate A/V sync.
if (mpctx->audio_status == STATUS_PLAYING && ao_untimed(mpctx->ao) &&
@@ -1157,13 +1250,14 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (audio_eof && !opts->gapless_audio)
playflags |= AOPLAY_FINAL_CHUNK;
- struct mp_audio data;
- mp_audio_buffer_peek(ao_c->ao_buffer, &data);
- if (audio_eof || data.samples >= align)
- data.samples = data.samples / align * align;
- data.samples = MPMIN(data.samples, mpctx->paused ? 0 : playsize);
- int played = write_to_ao(mpctx, &data, playflags);
- assert(played >= 0 && played <= data.samples);
+ uint8_t **planes;
+ int samples;
+ mp_audio_buffer_peek(ao_c->ao_buffer, &planes, &samples);
+ if (audio_eof || samples >= align)
+ samples = samples / align * align;
+ samples = MPMIN(samples, mpctx->paused ? 0 : playsize);
+ int played = write_to_ao(mpctx, planes, samples, playflags);
+ assert(played >= 0 && played <= samples);
mp_audio_buffer_skip(ao_c->ao_buffer, played);
mpctx->audio_drop_throttle =
diff --git a/player/client.c b/player/client.c
index c414654..618dbc4 100644
--- a/player/client.c
+++ b/player/client.c
@@ -479,7 +479,7 @@ static void *core_thread(void *tag)
static bool check_locale(void)
{
char *name = setlocale(LC_NUMERIC, NULL);
- return !name || strcmp(name, "C") == 0;
+ return !name || strcmp(name, "C") == 0 || strcmp(name, "C.UTF-8") == 0;
}
mpv_handle *mpv_create(void)
diff --git a/player/command.c b/player/command.c
index 1147eab..6288106 100644
--- a/player/command.c
+++ b/player/command.c
@@ -13,8 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#include <stdlib.h>
@@ -57,8 +55,8 @@
#include "video/out/vo.h"
#include "video/csputils.h"
#include "audio/aframe.h"
+#include "audio/format.h"
#include "audio/out/ao.h"
-#include "audio/filter/af.h"
#include "video/decode/dec_video.h"
#include "audio/decode/dec_audio.h"
#include "video/out/bitmap_packer.h"
@@ -71,6 +69,10 @@
#include "core.h"
+#if HAVE_LIBAF
+#include "audio/filter/af.h"
+#endif
+
#ifdef _WIN32
#include <windows.h>
#endif
@@ -325,15 +327,15 @@ static char *format_file_size(int64_t size)
return talloc_asprintf(NULL, "%.0f", s);
if (size < (1024 * 1024))
- return talloc_asprintf(NULL, "%.3f Kb", s / (1024.0));
+ return talloc_asprintf(NULL, "%.3f KiB", s / (1024.0));
if (size < (1024 * 1024 * 1024))
- return talloc_asprintf(NULL, "%.3f Mb", s / (1024.0 * 1024.0));
+ return talloc_asprintf(NULL, "%.3f MiB", s / (1024.0 * 1024.0));
if (size < (1024LL * 1024LL * 1024LL * 1024LL))
- return talloc_asprintf(NULL, "%.3f Gb", s / (1024.0 * 1024.0 * 1024.0));
+ return talloc_asprintf(NULL, "%.3f GiB", s / (1024.0 * 1024.0 * 1024.0));
- return talloc_asprintf(NULL, "%.3f Tb", s / (1024.0 * 1024.0 * 1024.0 * 1024.0));
+ return talloc_asprintf(NULL, "%.3f TiB", s / (1024.0 * 1024.0 * 1024.0 * 1024.0));
}
static char *format_delay(double time)
@@ -833,7 +835,7 @@ static bool time_remaining(MPContext *mpctx, double *remaining)
double len = get_time_length(mpctx);
double playback = get_playback_time(mpctx);
- if (playback == MP_NOPTS_VALUE)
+ if (playback == MP_NOPTS_VALUE || len <= 0)
return false;
*remaining = len - playback;
@@ -1455,10 +1457,12 @@ static int mp_property_filter_metadata(void *ctx, struct m_property *prop,
struct vf_chain *vf = mpctx->vo_chain->vf;
res = vf_control_by_label(vf, VFCTRL_GET_METADATA, &metadata, key);
} else if (strcmp(type, "af") == 0) {
+#if HAVE_LIBAF
if (!(mpctx->ao_chain && mpctx->ao_chain->af))
return M_PROPERTY_UNAVAILABLE;
struct af_stream *af = mpctx->ao_chain->af;
res = af_control_by_label(af, AF_CONTROL_GET_METADATA, &metadata, key);
+#endif
}
switch (res) {
case CONTROL_UNKNOWN:
@@ -1690,11 +1694,10 @@ static int mp_property_demuxer_cache_time(void *ctx, struct m_property *prop,
if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
return M_PROPERTY_UNAVAILABLE;
- double ts = s.ts_range[1];
- if (ts == MP_NOPTS_VALUE)
+ if (s.ts_end == MP_NOPTS_VALUE)
return M_PROPERTY_UNAVAILABLE;
- return m_property_double_ro(action, arg, ts);
+ return m_property_double_ro(action, arg, s.ts_end);
}
static int mp_property_demuxer_cache_idle(void *ctx, struct m_property *prop,
@@ -1711,6 +1714,51 @@ static int mp_property_demuxer_cache_idle(void *ctx, struct m_property *prop,
return m_property_flag_ro(action, arg, s.idle);
}
+static int mp_property_demuxer_cache_state(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ if (!mpctx->demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (action == M_PROPERTY_GET_TYPE) {
+ *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE};
+ return M_PROPERTY_OK;
+ }
+ if (action != M_PROPERTY_GET)
+ return M_PROPERTY_NOT_IMPLEMENTED;
+
+ struct demux_ctrl_reader_state s;
+ if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
+ return M_PROPERTY_UNAVAILABLE;
+
+ struct mpv_node *r = (struct mpv_node *)arg;
+ node_init(r, MPV_FORMAT_NODE_MAP, NULL);
+
+ struct mpv_node *ranges =
+ node_map_add(r, "seekable-ranges", MPV_FORMAT_NODE_ARRAY);
+ for (int n = 0; n < s.num_seek_ranges; n++) {
+ struct demux_seek_range *range = &s.seek_ranges[n];
+ struct mpv_node *sub = node_array_add(ranges, MPV_FORMAT_NODE_MAP);
+ node_map_add_double(sub, "start", range->start);
+ node_map_add_double(sub, "end", range->end);
+ }
+
+ if (s.ts_end != MP_NOPTS_VALUE)
+ node_map_add_double(r, "cache-end", s.ts_end);
+
+ if (s.ts_reader != MP_NOPTS_VALUE)
+ node_map_add_double(r, "reader-pts", s.ts_reader);
+
+ node_map_add_flag(r, "eof", s.eof);
+ node_map_add_flag(r, "underrun", s.underrun);
+ node_map_add_flag(r, "idle", s.idle);
+ node_map_add_int64(r, "total-bytes", s.total_bytes);
+ node_map_add_int64(r, "fw-bytes", s.fw_bytes);
+
+ return M_PROPERTY_OK;
+}
+
static int mp_property_demuxer_start_time(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -1785,8 +1833,7 @@ static int mp_property_mixer_active(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- struct ao_chain *ao_c = mpctx->ao_chain;
- return m_property_flag_ro(action, arg, ao_c && ao_c->af->initialized > 0);
+ return m_property_flag_ro(action, arg, !!mpctx->ao);
}
/// Volume (RW)
@@ -1965,17 +2012,6 @@ static int mp_property_ao(void *ctx, struct m_property *p, int action, void *arg
mpctx->ao ? ao_get_name(mpctx->ao) : NULL);
}
-static int mp_property_ao_detected_device(void *ctx,struct m_property *prop,
- int action, void *arg)
-{
- struct MPContext *mpctx = ctx;
- struct command_ctx *cmd = mpctx->command_ctx;
- create_hotplug(mpctx);
-
- const char *d = ao_hotplug_get_detected_device(cmd->hotplug);
- return m_property_strdup_ro(action, arg, d);
-}
-
/// Audio delay (RW)
static int mp_property_audio_delay(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -2064,35 +2100,6 @@ static int mp_property_audio_out_params(void *ctx, struct m_property *prop,
return r;
}
-/// Balance (RW)
-static int mp_property_balance(void *ctx, struct m_property *prop,
- int action, void *arg)
-{
- MPContext *mpctx = ctx;
-
- if (action == M_PROPERTY_PRINT) {
- char **str = arg;
- float bal = mpctx->opts->balance;
- if (bal == 0.f)
- *str = talloc_strdup(NULL, "center");
- else if (bal == -1.f)
- *str = talloc_strdup(NULL, "left only");
- else if (bal == 1.f)
- *str = talloc_strdup(NULL, "right only");
- else {
- unsigned right = (bal + 1.f) / 2.f * 100.f;
- *str = talloc_asprintf(NULL, "left %d%%, right %d%%",
- 100 - right, right);
- }
- return M_PROPERTY_OK;
- }
-
- int r = mp_property_generic_option(mpctx, prop, action, arg);
- if (action == M_PROPERTY_SET)
- audio_update_balance(mpctx);
- return r;
-}
-
static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
int direction, struct track *track)
{
@@ -2186,6 +2193,8 @@ static int property_switch_track_ff(void *ctx, struct m_property *prop,
*(int *) arg = track ? track->ff_index : -2;
return M_PROPERTY_OK;
case M_PROPERTY_SET: {
+ MP_WARN(mpctx, "Warning: property '%s' is deprecated and "
+ "will be removed in the future.\n", prop->name);
int id = *(int *)arg;
if (mpctx->playback_initialized) {
track = NULL;
@@ -2412,24 +2421,22 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop,
struct MPOpts *opts = mpctx->opts;
if (action == M_PROPERTY_SET) {
- int new = *(int *)arg;
+ char *new = *(char **)arg;
- if (opts->hwdec_api == new)
+ if (strcmp(opts->hwdec_api, new) == 0)
return M_PROPERTY_OK;
- opts->hwdec_api = new;
+ talloc_free(opts->hwdec_api);
+ opts->hwdec_api = talloc_strdup(NULL, new);
if (!vd)
return M_PROPERTY_OK;
- int current = -2;
- video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
- if (current != opts->hwdec_api) {
- video_vd_control(vd, VDCTRL_REINIT, NULL);
- double last_pts = mpctx->last_vo_pts;
- if (last_pts != MP_NOPTS_VALUE)
- queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0);
- }
+ video_vd_control(vd, VDCTRL_REINIT, NULL);
+ double last_pts = mpctx->last_vo_pts;
+ if (last_pts != MP_NOPTS_VALUE)
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0);
+
return M_PROPERTY_OK;
}
return mp_property_generic_option(mpctx, prop, action, arg);
@@ -2445,20 +2452,11 @@ static int mp_property_hwdec_current(void *ctx, struct m_property *prop,
if (!vd)
return M_PROPERTY_UNAVAILABLE;
- switch (action) {
- case M_PROPERTY_GET_TYPE: {
- // Abuse another hwdec option to resolve the value names
- struct m_property dummy = {.name = "hwdec"};
- return mp_property_generic_option(mpctx, &dummy, action, arg);
- }
- case M_PROPERTY_GET: {
- int current = HWDEC_NONE;
- video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
- *(int *)arg = current;
- return M_PROPERTY_OK;
- }
- }
- return M_PROPERTY_NOT_IMPLEMENTED;
+ char *current = NULL;
+ video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
+ if (!current)
+ current = "no";
+ return m_property_strdup_ro(action, arg, current);
}
static int mp_property_hwdec_interop(void *ctx, struct m_property *prop,
@@ -2468,14 +2466,10 @@ static int mp_property_hwdec_interop(void *ctx, struct m_property *prop,
if (!mpctx->video_out || !mpctx->video_out->hwdec_devs)
return M_PROPERTY_UNAVAILABLE;
- struct mp_hwdec_ctx *hwctx =
- hwdec_devices_get_first(mpctx->video_out->hwdec_devs);
-
- const char *name = hwctx ? hwctx->driver_name : NULL;
- if (!name && hwctx)
- name = m_opt_choice_str(mp_hwdec_names, hwctx->type);
-
- return m_property_strdup_ro(action, arg, name);
+ char *names = hwdec_devices_get_names(mpctx->video_out->hwdec_devs);
+ int res = m_property_strdup_ro(action, arg, names);
+ talloc_free(names);
+ return res;
}
/// Helper to set vo flags.
@@ -3257,14 +3251,14 @@ static int mp_property_dvb_channel(void *ctx, struct m_property *prop,
case M_PROPERTY_SET:
r = prop_stream_ctrl(mpctx, STREAM_CTRL_DVB_SET_CHANNEL, arg);
if (r == M_PROPERTY_OK && !mpctx->stop_play)
- mpctx->stop_play = PT_RELOAD_FILE;
+ mpctx->stop_play = PT_CURRENT_ENTRY;
return r;
case M_PROPERTY_SWITCH: {
struct m_property_switch_arg *sa = arg;
int dir = sa->inc >= 0 ? 1 : -1;
r = prop_stream_ctrl(mpctx, STREAM_CTRL_DVB_STEP_CHANNEL, &dir);
if (r == M_PROPERTY_OK && !mpctx->stop_play)
- mpctx->stop_play = PT_RELOAD_FILE;
+ mpctx->stop_play = PT_CURRENT_ENTRY;
return r;
}
case M_PROPERTY_GET_TYPE:
@@ -3283,14 +3277,14 @@ static int mp_property_dvb_channel_name(void *ctx, struct m_property *prop,
case M_PROPERTY_SET:
r = prop_stream_ctrl(mpctx, STREAM_CTRL_DVB_SET_CHANNEL_NAME, arg);
if (r == M_PROPERTY_OK && !mpctx->stop_play)
- mpctx->stop_play = PT_RELOAD_FILE;
+ mpctx->stop_play = PT_CURRENT_ENTRY;
return r;
case M_PROPERTY_SWITCH: {
struct m_property_switch_arg *sa = arg;
int dir = sa->inc >= 0 ? 1 : -1;
r = prop_stream_ctrl(mpctx, STREAM_CTRL_DVB_STEP_CHANNEL, &dir);
if (r == M_PROPERTY_OK && !mpctx->stop_play)
- mpctx->stop_play = PT_RELOAD_FILE;
+ mpctx->stop_play = PT_CURRENT_ENTRY;
return r;
}
case M_PROPERTY_GET: {
@@ -3944,6 +3938,7 @@ static const struct m_property mp_properties_base[] = {
{"demuxer-cache-time", mp_property_demuxer_cache_time},
{"demuxer-cache-idle", mp_property_demuxer_cache_idle},
{"demuxer-start-time", mp_property_demuxer_start_time},
+ {"demuxer-cache-state", mp_property_demuxer_cache_state},
{"cache-buffering-state", mp_property_cache_buffering},
{"paused-for-cache", mp_property_paused_for_cache},
{"demuxer-via-network", mp_property_demuxer_is_network},
@@ -3974,11 +3969,9 @@ static const struct m_property mp_properties_base[] = {
{"audio-params", mp_property_audio_params},
{"audio-out-params", mp_property_audio_out_params},
{"aid", mp_property_audio},
- {"balance", mp_property_balance},
{"audio-device", mp_property_audio_device},
{"audio-device-list", mp_property_audio_devices},
{"current-ao", mp_property_ao},
- {"audio-out-detected-device", mp_property_ao_detected_device},
// Video
{"fullscreen", mp_property_fullscreen},
@@ -4123,7 +4116,8 @@ static const char *const *const mp_event_property_change[] = {
"vo-delayed-frame-count", "mistimed-frame-count", "vsync-ratio",
"estimated-display-fps", "vsync-jitter", "sub-text", "audio-bitrate",
"video-bitrate", "sub-bitrate", "decoder-frame-drop-count",
- "frame-drop-count"),
+ "frame-drop-count", "video-frame-info"),
+ E(MP_EVENT_DURATION_UPDATE, "duration"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
"width", "height", "fps", "aspect", "vo-configured", "current-vo",
@@ -4131,7 +4125,7 @@ static const char *const *const mp_event_property_change[] = {
"colormatrix-primaries", "video-aspect", "video-dec-params",
"hwdec", "hwdec-current", "hwdec-interop"),
E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
- "samplerate", "channels", "audio", "volume", "mute", "balance",
+ "samplerate", "channels", "audio", "volume", "mute",
"current-ao", "audio-codec-name", "audio-params",
"audio-out-params", "volume-max", "mixer-active"),
E(MPV_EVENT_SEEK, "seeking", "core-idle", "eof-reached"),
@@ -4325,7 +4319,6 @@ static const struct property_osd_display {
{ "ao-mute", "AO Mute" },
{ "audio-delay", "A-V delay" },
{ "audio", "Audio" },
- { "balance", "Balance", .osd_progbar = OSD_BALANCE },
// video
{ "panscan", "Panscan", .osd_progbar = OSD_PANSCAN },
{ "taskbar-progress", "Progress in taskbar" },
@@ -4855,7 +4848,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
int osdl = msg_osd ? 1 : OSD_LEVEL_INVISIBLE;
bool async = cmd->flags & MP_ASYNC_CMD;
- mp_cmd_dump(mpctx->log, cmd->id == MP_CMD_IGNORE ? MSGL_DEBUG : MSGL_V,
+ mp_cmd_dump(mpctx->log, cmd->id == MP_CMD_IGNORE ? MSGL_TRACE : MSGL_DEBUG,
"Run command:", cmd);
if (cmd->flags & MP_EXPAND_PROPERTIES) {
@@ -4964,8 +4957,6 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
break;
}
-#if HAVE_GPL
- // Possibly GPL due to 7a71da01d64374ce22b430590f3df32c881288bd.
case MP_CMD_ADD:
case MP_CMD_CYCLE:
{
@@ -4995,14 +4986,12 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
return -1;
} else if (r <= 0) {
set_osd_msg(mpctx, osdl, osd_duration,
- "Failed to increment property '%s' by %g",
- property, s.inc);
+ "Failed to change property '%s'", property);
return -1;
}
}
break;
}
-#endif
case MP_CMD_MULTIPLY: {
char *property = cmd->args[0].v.s;
@@ -5293,8 +5282,6 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
break;
}
-#if HAVE_GPL
- // Possibly GPL due to 2f376d1b39913e8ff4c4499e7cf7148ec331d4db.
case MP_CMD_SUB_ADD:
case MP_CMD_AUDIO_ADD: {
if (!mpctx->playing)
@@ -5346,7 +5333,6 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
print_track_list(mpctx, "Track removed:");
break;
}
-#endif
case MP_CMD_SUB_RELOAD:
case MP_CMD_AUDIO_RELOAD: {
@@ -5491,11 +5477,13 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
return vf_send_command(mpctx->vo_chain->vf, cmd->args[0].v.s,
cmd->args[1].v.s, cmd->args[2].v.s);
+#if HAVE_LIBAF
case MP_CMD_AF_COMMAND:
if (!mpctx->ao_chain)
return -1;
return af_send_command(mpctx->ao_chain->af, cmd->args[0].v.s,
cmd->args[1].v.s, cmd->args[2].v.s);
+#endif
case MP_CMD_SCRIPT_BINDING: {
mpv_event_client_message event = {0};
@@ -5766,10 +5754,8 @@ void handle_command_updates(struct MPContext *mpctx)
// This is a bit messy: ao_hotplug wakes up the player, and then we have
// to recheck the state. Then the client(s) will read the property.
- if (ctx->hotplug && ao_hotplug_check_update(ctx->hotplug)) {
+ if (ctx->hotplug && ao_hotplug_check_update(ctx->hotplug))
mp_notify_property(mpctx, "audio-device-list");
- mp_notify_property(mpctx, "audio-out-detected-device");
- }
}
void mp_notify(struct MPContext *mpctx, int event, void *arg)
diff --git a/player/command.h b/player/command.h
index c941042..478bc8c 100644
--- a/player/command.h
+++ b/player/command.h
@@ -13,8 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#ifndef MPLAYER_COMMAND_H
@@ -59,6 +57,7 @@ enum {
MP_EVENT_WIN_STATE,
MP_EVENT_CHANGE_PLAYLIST,
MP_EVENT_CORE_IDLE,
+ MP_EVENT_DURATION_UPDATE,
};
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);
diff --git a/player/core.h b/player/core.h
index 1b08fed..0b5f610 100644
--- a/player/core.h
+++ b/player/core.h
@@ -200,6 +200,7 @@ struct ao_chain {
bool pts_reset;
struct af_stream *af;
+ struct mp_aconverter *conv; // if af unavailable
struct ao *ao;
struct mp_audio_buffer *ao_buffer;
double ao_resume_time;
@@ -207,9 +208,14 @@ struct ao_chain {
// 1-element input frame queue.
struct mp_aframe *input_frame;
+ // 1-element output frame queue.
+ struct mp_aframe *output_frame;
+
// Last known input_mpi format (so af can be reinitialized any time).
struct mp_aframe *input_format;
+ struct mp_aframe *filter_input_format;
+
struct track *track;
struct lavfi_pad *filter_src;
struct dec_audio *audio_src;
@@ -386,7 +392,6 @@ typedef struct MPContext {
struct frame_info *past_frames;
int num_past_frames;
- double next_heartbeat;
double last_idle_tick;
double next_cache_update;
@@ -537,6 +542,8 @@ void issue_refresh_seek(struct MPContext *mpctx, enum seek_precision min_prec);
// misc.c
double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t);
double get_play_end_pts(struct MPContext *mpctx);
+double get_play_start_pts(struct MPContext *mpctx);
+double get_ab_loop_start_time(struct MPContext *mpctx);
void merge_playlist_files(struct playlist *pl);
float mp_get_cache_percent(struct MPContext *mpctx);
bool mp_get_cache_idle(struct MPContext *mpctx);
diff --git a/player/external_files.c b/player/external_files.c
index f55e52b..4798439 100644
--- a/player/external_files.c
+++ b/player/external_files.c
@@ -123,10 +123,6 @@ static void append_dir_subtitles(struct mpv_global *global,
if (f_fbname.start != f_fname.start)
talloc_steal(tmpmem, f_fname.start);
- // 0 = nothing
- // 1 = any subtitle file
- // 2 = any sub file containing movie name
- // 3 = sub file containing movie name and the lang extension
char *path0 = bstrdup0(tmpmem, path);
if (mp_is_url(bstr0(path0)))
@@ -170,24 +166,25 @@ static void append_dir_subtitles(struct mpv_global *global,
goto next_sub;
// we have a (likely) subtitle file
+ // 0 = nothing
+ // 1 = any subtitle file
+ // 2 = any sub file containing movie name
+ // 3 = sub file containing movie name and the lang extension
int prio = 0;
- char *found_lang = NULL;
- if (langs) {
- if (bstr_startswith(tmp_fname_trim, f_fname_trim)) {
- struct bstr lang = guess_lang_from_filename(tmp_fname_trim);
- if (lang.len) {
- for (int n = 0; langs[n]; n++) {
- if (bstr_startswith0(lang, langs[n])) {
- prio = 4; // matches the movie name + lang extension
- found_lang = langs[n];
- break;
- }
- }
- }
+
+ bstr lang = {0};
+ if (bstr_startswith(tmp_fname_trim, f_fname_trim))
+ lang = guess_lang_from_filename(tmp_fname_trim);
+ for (int n = 0; langs && langs[n]; n++) {
+ if (lang.len && bstr_case_startswith(lang, bstr0(langs[n]))) {
+ prio = 4; // matches the movie name + lang extension
+ break;
}
}
if (!prio && bstrcmp(tmp_fname_trim, f_fname_trim) == 0)
prio = 3; // matches the movie name
+ if (!prio && lang.len)
+ prio = 3; // matches the movie name + a language was matched
if (!prio && bstr_find(tmp_fname_trim, f_fname_trim) >= 0 && fuzz >= 1)
prio = 2; // contains the movie name
if (!prio) {
@@ -215,7 +212,7 @@ static void append_dir_subtitles(struct mpv_global *global,
sub->type = type;
sub->priority = prio;
sub->fname = subpath;
- sub->lang = found_lang;
+ sub->lang = lang.len ? bstrdup0(*slist, lang) : NULL;
} else
talloc_free(subpath);
}
diff --git a/player/javascript.c b/player/javascript.c
index abb1581..3de900b 100644
--- a/player/javascript.c
+++ b/player/javascript.c
@@ -616,7 +616,7 @@ static void script_set_property_native(js_State *J, void *af)
mpv_node node;
makenode(af, &node, J, 2);
mpv_handle *h = jclient(J);
- int e = mpv_get_property(h, js_tostring(J, 1), MPV_FORMAT_NODE, &node);
+ int e = mpv_set_property(h, js_tostring(J, 1), MPV_FORMAT_NODE, &node);
push_status(J, e);
}
@@ -647,7 +647,7 @@ static void script_set_property_number(js_State *J)
{
double v = js_tonumber(J, 2);
mpv_handle *h = jclient(J);
- int e = mpv_get_property(h, js_tostring(J, 1), MPV_FORMAT_DOUBLE, &v);
+ int e = mpv_set_property(h, js_tostring(J, 1), MPV_FORMAT_DOUBLE, &v);
push_status(J, e);
}
@@ -836,6 +836,41 @@ static void script_readdir(js_State *J, void *af)
}
}
+static void script_file_info(js_State *J)
+{
+ const char *path = js_tostring(J, 1);
+
+ struct stat statbuf;
+ if (stat(path, &statbuf) != 0) {
+ push_failure(J, "Cannot stat path");
+ return;
+ }
+ // Clear last error
+ set_last_error(jctx(J), 0, NULL);
+
+ const char * stat_names[] = {
+ "mode", "size",
+ "atime", "mtime", "ctime", NULL
+ };
+ const double stat_values[] = {
+ statbuf.st_mode,
+ statbuf.st_size,
+ statbuf.st_atime,
+ statbuf.st_mtime,
+ statbuf.st_ctime
+ };
+ // Create an object and add all fields
+ push_nums_obj(J, stat_names, stat_values);
+
+ // Convenience booleans
+ js_pushboolean(J, S_ISREG(statbuf.st_mode));
+ js_setproperty(J, -2, "is_file");
+
+ js_pushboolean(J, S_ISDIR(statbuf.st_mode));
+ js_setproperty(J, -2, "is_dir");
+}
+
+
static void script_split_path(js_State *J)
{
const char *p = js_tostring(J, 1);
@@ -1255,6 +1290,7 @@ static const struct fn_entry main_fns[] = {
static const struct fn_entry utils_fns[] = {
AF_ENTRY(readdir, 2),
+ FN_ENTRY(file_info, 1),
FN_ENTRY(split_path, 1),
AF_ENTRY(join_path, 2),
AF_ENTRY(get_user_path, 1),
diff --git a/player/javascript/defaults.js b/player/javascript/defaults.js
index f35add5..56c83f8 100644
--- a/player/javascript/defaults.js
+++ b/player/javascript/defaults.js
@@ -7,7 +7,7 @@
mp.msg = { log: mp.log };
mp.msg.verbose = mp.log.bind(null, "v");
-var levels = ["fatal", "error", "warn", "info", "debug"];
+var levels = ["fatal", "error", "warn", "info", "debug", "trace"];
levels.forEach(function(l) { mp.msg[l] = mp.log.bind(null, l) });
// same as {} but without inherited stuff, e.g. o["toString"] doesn't exist.
diff --git a/player/lavfi.c b/player/lavfi.c
index e5bfe7f..2ceb550 100644
--- a/player/lavfi.c
+++ b/player/lavfi.c
@@ -367,7 +367,7 @@ static bool init_pads(struct lavfi *c)
goto error; // can happen if pad reassociation fails
if (pad->dir == LAVFI_OUT) {
- AVFilter *dst_filter = NULL;
+ const AVFilter *dst_filter = NULL;
if (pad->type == STREAM_AUDIO) {
dst_filter = avfilter_get_by_name("abuffersink");
} else if (pad->type == STREAM_VIDEO) {
@@ -453,7 +453,7 @@ static bool init_pads(struct lavfi *c)
assert(0);
}
- AVFilter *filter = avfilter_get_by_name(filter_name);
+ const AVFilter *filter = avfilter_get_by_name(filter_name);
if (filter) {
char name[256];
snprintf(name, sizeof(name), "mpv_src_%s", pad->name);
diff --git a/player/loadfile.c b/player/loadfile.c
index 543fd02..d2e26c1 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -212,6 +212,8 @@ void update_demuxer_properties(struct MPContext *mpctx)
mpctx->filtered_tags = info;
mp_notify(mpctx, MPV_EVENT_METADATA_UPDATE, NULL);
}
+ if (events & DEMUX_EVENT_DURATION)
+ mp_notify(mpctx, MP_EVENT_DURATION_UPDATE, NULL);
demuxer->events = 0;
}
@@ -296,7 +298,7 @@ void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer)
static int match_lang(char **langs, char *lang)
{
for (int idx = 0; langs && langs[idx]; idx++) {
- if (lang && strcmp(langs[idx], lang) == 0)
+ if (lang && strcasecmp(langs[idx], lang) == 0)
return INT_MAX - idx;
}
return 0;
@@ -598,26 +600,37 @@ struct track *mp_add_external_file(struct MPContext *mpctx, char *filename,
goto err_out;
enable_demux_thread(mpctx, demuxer);
- if (filter != STREAM_SUB && opts->rebase_start_time)
+ if (opts->rebase_start_time)
demux_set_ts_offset(demuxer, -demuxer->start_time);
- struct track *first = NULL;
+ bool has_any = false;
for (int n = 0; n < demux_get_num_stream(demuxer); n++) {
struct sh_stream *sh = demux_get_stream(demuxer, n);
- if (filter == STREAM_TYPE_COUNT || sh->type == filter) {
- struct track *t = add_stream_track(mpctx, demuxer, sh);
- t->is_external = true;
- t->title = talloc_strdup(t, mp_basename(disp_filename));
- t->external_filename = talloc_strdup(t, filename);
- first = t;
- // --external-file special semantics
- t->no_default = filter == STREAM_TYPE_COUNT;
+ if (sh->type == filter || filter == STREAM_TYPE_COUNT) {
+ has_any = true;
+ break;
}
}
- if (!first) {
+
+ if (!has_any) {
free_demuxer_and_stream(demuxer);
- MP_WARN(mpctx, "No streams added from file %s.\n", disp_filename);
- goto err_out;
+ char *tname = mp_tprintf(20, "%s ", stream_type_name(filter));
+ if (filter == STREAM_TYPE_COUNT)
+ tname = "";
+ MP_ERR(mpctx, "No %sstreams in file %s.\n", tname, disp_filename);
+ return false;
+ }
+
+ struct track *first = NULL;
+ for (int n = 0; n < demux_get_num_stream(demuxer); n++) {
+ struct sh_stream *sh = demux_get_stream(demuxer, n);
+ struct track *t = add_stream_track(mpctx, demuxer, sh);
+ t->is_external = true;
+ t->title = talloc_strdup(t, mp_basename(disp_filename));
+ t->external_filename = talloc_strdup(t, filename);
+ t->no_default = sh->type != filter;
+ if (!first && (filter == STREAM_TYPE_COUNT || sh->type == filter))
+ first = t;
}
return first;
@@ -895,7 +908,7 @@ static void open_demux_reentrant(struct MPContext *mpctx)
if (done) {
MP_VERBOSE(mpctx, "Dropping finished prefetch of wrong URL.\n");
} else {
- MP_VERBOSE(mpctx, "Aborting onging prefetch of wrong URL.\n");
+ MP_VERBOSE(mpctx, "Aborting ongoing prefetch of wrong URL.\n");
}
cancel_open(mpctx);
}
@@ -1060,7 +1073,7 @@ static int reinit_complex_filters(struct MPContext *mpctx, bool force_uninit)
{
if (mpctx->vo_chain) {
if (mpctx->vo_chain->video_src) {
- MP_ERR(mpctx, "Pad vo tries to connected to already used VO.\n");
+ MP_ERR(mpctx, "Pad vo tries to connect to already used VO.\n");
goto done;
}
} else {
@@ -1080,7 +1093,7 @@ static int reinit_complex_filters(struct MPContext *mpctx, bool force_uninit)
{
if (mpctx->ao_chain) {
if (mpctx->ao_chain->audio_src) {
- MP_ERR(mpctx, "Pad ao tries to connected to already used AO.\n");
+ MP_ERR(mpctx, "Pad ao tries to connect to already used AO.\n");
goto done;
}
} else {
@@ -1163,6 +1176,9 @@ static void play_current_file(struct MPContext *mpctx)
reset_playback_state(mpctx);
+ // let get_current_time() show 0 as start time (before playback_pts is set)
+ mpctx->last_seek_pts = 0.0;
+
mpctx->playing = mpctx->playlist->current;
if (!mpctx->playing || !mpctx->playing->filename)
goto terminate_playback;
@@ -1193,10 +1209,7 @@ static void play_current_file(struct MPContext *mpctx)
load_per_file_options(mpctx->mconfig, mpctx->playing->params,
mpctx->playing->num_params);
-#if HAVE_GPL
- // Possibly GPL due to d8fd7131bbcde029ab41799fd3162050b43f6848.
mpctx->max_frames = opts->play_frames;
-#endif
handle_force_window(mpctx, false);
@@ -1323,27 +1336,23 @@ reopen_file:
mp_notify(mpctx, MPV_EVENT_FILE_LOADED, NULL);
update_screensaver_state(mpctx);
-#if HAVE_GPL
- // Possibly GPL due to d8fd7131bbcde029ab41799fd3162050b43f6848.
if (mpctx->max_frames == 0) {
if (!mpctx->stop_play)
mpctx->stop_play = PT_NEXT_ENTRY;
mpctx->error_playing = 0;
goto terminate_playback;
}
-#endif
- double startpos = rel_time_to_abs(mpctx, opts->play_start);
- if (startpos == MP_NOPTS_VALUE && opts->chapterrange[0] > 0) {
- double start = chapter_start_time(mpctx, opts->chapterrange[0] - 1);
- if (start != MP_NOPTS_VALUE)
- startpos = start;
- }
- if (startpos != MP_NOPTS_VALUE) {
- if (!opts->rebase_start_time) {
- startpos += mpctx->demuxer->start_time;
+ double play_start_pts = get_play_start_pts(mpctx);
+ if (play_start_pts != MP_NOPTS_VALUE) {
+ /*
+ * get_play_start_pts returns rebased values, but
+ * we want an un rebased value to feed to seeker.
+ */
+ if (!opts->rebase_start_time){
+ play_start_pts += mpctx->demuxer->start_time;
}
- queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, MPSEEK_DEFAULT, 0);
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, play_start_pts, MPSEEK_DEFAULT, 0);
execute_queued_seek(mpctx);
}
diff --git a/player/lua.c b/player/lua.c
index b3e4fe8..d928fdf 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -69,6 +69,9 @@ static const char * const builtin_lua_scripts[][2] = {
{"@ytdl_hook.lua",
# include "player/lua/ytdl_hook.inc"
},
+ {"@stats.lua",
+# include "player/lua/stats.inc"
+ },
{0}
};
@@ -190,7 +193,7 @@ static void add_functions(struct script_ctx *ctx);
static void load_file(lua_State *L, const char *fname)
{
struct script_ctx *ctx = get_ctx(L);
- MP_VERBOSE(ctx, "loading file %s\n", fname);
+ MP_DBG(ctx, "loading file %s\n", fname);
int r = luaL_loadfile(L, fname);
if (r)
lua_error(L);
@@ -219,7 +222,7 @@ static int load_builtin(lua_State *L)
static void require(lua_State *L, const char *name)
{
struct script_ctx *ctx = get_ctx(L);
- MP_VERBOSE(ctx, "loading %s\n", name);
+ MP_DBG(ctx, "loading %s\n", name);
// Lazy, but better than calling the "require" function manually
char buf[80];
snprintf(buf, sizeof(buf), "require '%s'", name);
@@ -1082,6 +1085,48 @@ static int script_readdir(lua_State *L)
return 1;
}
+static int script_file_info(lua_State *L)
+{
+ const char *path = luaL_checkstring(L, 1);
+
+ struct stat statbuf;
+ if (stat(path, &statbuf) != 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, "error");
+ return 2;
+ }
+
+ lua_newtable(L); // Result stat table
+
+ const char * stat_names[] = {
+ "mode", "size",
+ "atime", "mtime", "ctime", NULL
+ };
+ const unsigned int stat_values[] = {
+ statbuf.st_mode,
+ statbuf.st_size,
+ statbuf.st_atime,
+ statbuf.st_mtime,
+ statbuf.st_ctime
+ };
+
+ // Add all fields
+ for (int i = 0; stat_names[i]; i++) {
+ lua_pushinteger(L, stat_values[i]);
+ lua_setfield(L, -2, stat_names[i]);
+ }
+
+ // Convenience booleans
+ lua_pushboolean(L, S_ISREG(statbuf.st_mode));
+ lua_setfield(L, -2, "is_file");
+
+ lua_pushboolean(L, S_ISDIR(statbuf.st_mode));
+ lua_setfield(L, -2, "is_dir");
+
+ // Return table
+ return 1;
+}
+
static int script_split_path(lua_State *L)
{
const char *p = luaL_checkstring(L, 1);
@@ -1288,6 +1333,7 @@ static const struct fn_entry main_fns[] = {
static const struct fn_entry utils_fns[] = {
FN_ENTRY(readdir),
+ FN_ENTRY(file_info),
FN_ENTRY(split_path),
FN_ENTRY(join_path),
FN_ENTRY(subprocess),
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua
index 6669cf3..1320522 100644
--- a/player/lua/defaults.lua
+++ b/player/lua/defaults.lua
@@ -440,6 +440,7 @@ mp.msg = {
info = function(...) return mp.log("info", ...) end,
verbose = function(...) return mp.log("v", ...) end,
debug = function(...) return mp.log("debug", ...) end,
+ trace = function(...) return mp.log("trace", ...) end,
}
_G.print = mp.msg.info
diff --git a/player/lua/osc.lua b/player/lua/osc.lua
index e4faf6c..a0f37f4 100644
--- a/player/lua/osc.lua
+++ b/player/lua/osc.lua
@@ -594,6 +594,16 @@ function render_elements(master_ass)
end
end
+ -- seek ranges
+ local seekRanges = element.slider.seekRangesF()
+ if not (seekRanges == nil) then
+ for _,range in pairs(seekRanges) do
+ local pstart = get_slider_ele_pos_for(element, range["start"])
+ local pend = get_slider_ele_pos_for(element, range["end"])
+ elem_ass:rect_ccw(pstart, (elem_geo.h/2)-1, pend, (elem_geo.h/2) + 1)
+ end
+ end
+
elem_ass:draw_stop()
-- add tooltip
@@ -1746,6 +1756,22 @@ function osc_init()
return ""
end
end
+ ne.slider.seekRangesF = function()
+ local cache_state = mp.get_property_native("demuxer-cache-state", nil)
+ if not cache_state then
+ return nil
+ end
+ local duration = mp.get_property_number("duration", nil)
+ if (duration == nil) or duration <= 0 then
+ return nil
+ end
+ local ranges = cache_state["seekable-ranges"]
+ for _, range in pairs(ranges) do
+ range["start"] = 100 * range["start"] / duration
+ range["end"] = 100 * range["end"] / duration
+ end
+ return ranges
+ end
ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged
function (element)
-- mouse move events may pile up during seeking and may still get
@@ -1809,12 +1835,16 @@ function osc_init()
ne.content = function ()
local dmx_cache = mp.get_property_number("demuxer-cache-duration")
- local cache_used = mp.get_property_number("cache-used")
+ local cache_used = mp.get_property_number("cache-used", 0)
+ local dmx_cache_state = mp.get_property_native("demuxer-cache-state", {})
local is_network = mp.get_property_native("demuxer-via-network")
if dmx_cache then
dmx_cache = string.format("%3.0fs", dmx_cache)
end
- if cache_used then
+ if dmx_cache_state["fw-bytes"] then
+ cache_used = cache_used + dmx_cache_state["fw-bytes"] / 1024
+ end
+ if cache_used > 0 then
local suffix = " KiB"
if (cache_used >= 1024) then
cache_used = cache_used/1024
@@ -1876,7 +1906,7 @@ function show_osc()
-- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding
if not state.enabled then return end
- msg.debug("show_osc")
+ msg.trace("show_osc")
--remember last time of invocation (mouse move)
state.showtime = mp.get_time()
@@ -1888,13 +1918,13 @@ function show_osc()
end
function hide_osc()
- msg.debug("hide_osc")
+ msg.trace("hide_osc")
if not state.enabled then
-- typically hide happens at render() from tick(), but now tick() is
-- no-op and won't render again to remove the osc, so do that manually.
state.osc_visible = false
timer_stop()
- render() -- state.osc_visible == false -> remove the osc from screen
+ render_wipe()
elseif (user_opts.fadeduration > 0) then
if not(state.osc_visible == false) then
state.anitype = "out"
@@ -1932,7 +1962,7 @@ end
function timer_start()
if not (state.timer_active) then
- msg.debug("timer start")
+ msg.trace("timer start")
if (state.timer == nil) then
-- create new timer
@@ -1948,7 +1978,7 @@ end
function timer_stop()
if (state.timer_active) then
- msg.debug("timer stop")
+ msg.trace("timer stop")
if not (state.timer == nil) then
-- kill timer
@@ -1973,8 +2003,13 @@ function request_init()
state.initREQ = true
end
+function render_wipe()
+ msg.trace("render_wipe()")
+ mp.set_osd_ass(0, 0, "{}")
+end
+
function render()
- msg.debug("rendering")
+ msg.trace("rendering")
local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size()
local mouseX, mouseY = get_virt_mouse_pos()
local now = mp.get_time()
@@ -2177,7 +2212,7 @@ function tick()
if (state.idle) then
-- render idle message
- msg.debug("idle message")
+ msg.trace("idle message")
local icon_x, icon_y = 320 - 26, 140
local ass = assdraw.ass_new()
@@ -2356,6 +2391,6 @@ end
visibility_mode(user_opts.visibility, true)
mp.register_script_message("osc-visibility", visibility_mode)
-mp.add_key_binding("del", function() visibility_mode("cycle") end)
+mp.add_key_binding(nil, "visibility", function() visibility_mode("cycle") end)
set_virt_mouse_area(0, 0, 0, 0, "input")
diff --git a/player/lua/stats.lua b/player/lua/stats.lua
new file mode 100644
index 0000000..bdc3338
--- /dev/null
+++ b/player/lua/stats.lua
@@ -0,0 +1,735 @@
+-- Display some stats.
+--
+-- Please consult the readme for information about usage and configuration:
+-- https://github.com/Argon-/mpv-stats
+--
+-- Please note: not every property is always available and therefore not always
+-- visible.
+
+local mp = require 'mp'
+local options = require 'mp.options'
+
+-- Options
+local o = {
+ -- Default key bindings
+ key_oneshot = "i",
+ key_toggle = "I",
+ key_page_1 = "1",
+ key_page_2 = "2",
+ key_page_3 = "3",
+
+ duration = 4,
+ redraw_delay = 1, -- acts as duration in the toggling case
+ ass_formatting = true,
+ persistent_overlay = false, -- whether the stats can be overwritten by other output
+ print_perfdata_passes = false, -- when true, print the full information about all passes
+ filter_params_max_length = 100, -- a filter list longer than this many characters will be shown one filter per line instead
+ debug = false,
+
+ -- Graph options and style
+ plot_perfdata = true,
+ plot_vsync_ratio = true,
+ plot_vsync_jitter = true,
+ skip_frames = 5,
+ global_max = true,
+ flush_graph_data = true, -- clear data buffers when toggling
+ plot_bg_border_color = "0000FF",
+ plot_bg_color = "262626",
+ plot_color = "FFFFFF",
+
+ -- Text style
+ font = "Source Sans Pro",
+ font_mono = "Source Sans Pro", -- monospaced digits are sufficient
+ font_size = 8,
+ font_color = "FFFFFF",
+ border_size = 0.8,
+ border_color = "262626",
+ shadow_x_offset = 0.0,
+ shadow_y_offset = 0.0,
+ shadow_color = "000000",
+ alpha = "11",
+
+ -- Custom header for ASS tags to style the text output.
+ -- Specifying this will ignore the text style values above and just
+ -- use this string instead.
+ custom_header = "",
+
+ -- Text formatting
+ -- With ASS
+ ass_nl = "\\N",
+ ass_indent = "\\h\\h\\h\\h\\h",
+ ass_prefix_sep = "\\h\\h",
+ ass_b1 = "{\\b1}",
+ ass_b0 = "{\\b0}",
+ ass_it1 = "{\\i1}",
+ ass_it0 = "{\\i0}",
+ -- Without ASS
+ no_ass_nl = "\n",
+ no_ass_indent = "\t",
+ no_ass_prefix_sep = " ",
+ no_ass_b1 = "\027[1m",
+ no_ass_b0 = "\027[0m",
+ no_ass_it1 = "\027[3m",
+ no_ass_it0 = "\027[0m",
+}
+options.read_options(o)
+
+local format = string.format
+local max = math.max
+local min = math.min
+
+-- Function used to record performance data
+local recorder = nil
+-- Timer used for redrawing (toggling) and clearing the screen (oneshot)
+local display_timer = nil
+-- Current page and <page key>:<page function> mappings
+local curr_page = o.key_page_1
+local pages = {}
+-- Save these sequences locally as we'll need them a lot
+local ass_start = mp.get_property_osd("osd-ass-cc/0")
+local ass_stop = mp.get_property_osd("osd-ass-cc/1")
+-- Ring buffers for the values used to construct a graph.
+-- .pos denotes the current position, .len the buffer length
+-- .max is the max value in the corresponding buffer
+local vsratio_buf, vsjitter_buf
+local function init_buffers()
+ vsratio_buf = {0, pos = 1, len = 50, max = 0}
+ vsjitter_buf = {0, pos = 1, len = 50, max = 0}
+end
+-- Save all properties known to this version of mpv
+local property_list = {}
+for p in string.gmatch(mp.get_property("property-list"), "([^,]+)") do property_list[p] = true end
+-- Mapping of properties to their deprecated names
+local property_aliases = {
+ ["decoder-frame-drop-count"] = "drop-frame-count",
+ ["frame-drop-count"] = "vo-drop-frame-count",
+ ["container-fps"] = "fps",
+}
+
+
+-- Return deprecated name for the given property
+local function compat(p)
+ while not property_list[p] and property_aliases[p] do
+ p = property_aliases[p]
+ end
+ return p
+end
+
+
+local function set_ASS(b)
+ if not o.use_ass or o.persistent_overlay then
+ return ""
+ end
+ return b and ass_start or ass_stop
+end
+
+
+local function no_ASS(t)
+ return set_ASS(false) .. t .. set_ASS(true)
+end
+
+
+local function b(t)
+ return o.b1 .. t .. o.b0
+end
+
+
+local function it(t)
+ return o.it1 .. t .. o.it0
+end
+
+
+local function text_style()
+ if not o.use_ass then
+ return ""
+ end
+ if o.custom_header and o.custom_header ~= "" then
+ return set_ASS(true) .. o.custom_header
+ else
+ return format("%s{\\r}{\\an7}{\\fs%d}{\\fn%s}{\\bord%f}{\\3c&H%s&}" ..
+ "{\\1c&H%s&}{\\alpha&H%s&}{\\xshad%f}{\\yshad%f}{\\4c&H%s&}",
+ set_ASS(true), o.font_size, o.font, o.border_size,
+ o.border_color, o.font_color, o.alpha, o.shadow_x_offset,
+ o.shadow_y_offset, o.shadow_color)
+ end
+end
+
+
+local function has_vo_window()
+ return mp.get_property("vo-configured") == "yes"
+end
+
+
+local function has_video()
+ local r = mp.get_property("video")
+ return r and r ~= "no" and r ~= ""
+end
+
+
+local function has_audio()
+ local r = mp.get_property("audio")
+ return r and r ~= "no" and r ~= ""
+end
+
+
+local function has_ansi()
+ local is_windows = type(package) == 'table'
+ and type(package.config) == 'string'
+ and package.config:sub(1, 1) == '\\'
+ if is_windows then
+ return os.getenv("ANSICON")
+ end
+ return true
+end
+
+
+-- Generate a graph from the given values.
+-- Returns an ASS formatted vector drawing as string.
+--
+-- values: Array/table of numbers representing the data. Used like a ring buffer
+-- it will get iterated backwards `len` times starting at position `i`.
+-- i : Index of the latest data value in `values`.
+-- len : The length/amount of numbers in `values`.
+-- v_max : The maximum number in `values`. It is used to scale all data
+-- values to a range of 0 to `v_max`.
+-- v_avg : The average number in `values`. It is used to try and center graphs
+-- if possible. May be left as nil
+-- scale : A value that will be multiplied with all data values.
+-- x_tics: Horizontal width multiplier for the steps
+local function generate_graph(values, i, len, v_max, v_avg, scale, x_tics)
+ -- Check if at least one value exists
+ if not values[i] then
+ return ""
+ end
+
+ local x_max = (len - 1) * x_tics
+ local y_offset = o.border_size
+ local y_max = o.font_size * 0.66
+ local x = 0
+
+ -- try and center the graph if possible, but avoid going above `scale`
+ if v_avg then
+ scale = min(scale, v_max / (2 * v_avg))
+ end
+
+ local s = {format("m 0 0 n %f %f l ", x, y_max - (y_max * values[i] / v_max * scale))}
+ i = ((i - 2) % len) + 1
+
+ for p = 1, len - 1 do
+ if values[i] then
+ x = x - x_tics
+ s[#s+1] = format("%f %f ", x, y_max - (y_max * values[i] / v_max * scale))
+ end
+ i = ((i - 2) % len) + 1
+ end
+
+ s[#s+1] = format("%f %f %f %f", x, y_max, 0, y_max)
+
+ local bg_box = format("{\\bord0.5}{\\3c&H%s&}{\\1c&H%s&}m 0 %f l %f %f %f 0 0 0",
+ o.plot_bg_border_color, o.plot_bg_color, y_max, x_max, y_max, x_max)
+ return format("%s{\\r}{\\pbo%f}{\\shad0}{\\alpha&H00}{\\p1}%s{\\p0}{\\bord0}{\\1c&H%s}{\\p1}%s{\\p0}%s",
+ o.prefix_sep, y_offset, bg_box, o.plot_color, table.concat(s), text_style())
+end
+
+
+-- Format and append a property.
+-- A property whose value is either `nil` or empty (hereafter called "invalid")
+-- is skipped and not appended.
+-- Returns `false` in case nothing was appended, otherwise `true`.
+--
+-- s : Table containing strings.
+-- prop : The property to query and format (based on its OSD representation).
+-- attr : Optional table to overwrite certain (formatting) attributes for
+-- this property.
+-- exclude: Optional table containing keys which are considered invalid values
+-- for this property. Specifying this will replace empty string as
+-- default invalid value (nil is always invalid).
+local function append_property(s, prop, attr, excluded)
+ excluded = excluded or {[""] = true}
+ local ret = mp.get_property_osd(prop)
+ if not ret or excluded[ret] then
+ if o.debug then
+ print("No value for property: " .. prop)
+ end
+ return false
+ end
+
+ attr.prefix_sep = attr.prefix_sep or o.prefix_sep
+ attr.indent = attr.indent or o.indent
+ attr.nl = attr.nl or o.nl
+ attr.suffix = attr.suffix or ""
+ attr.prefix = attr.prefix or ""
+ attr.no_prefix_markup = attr.no_prefix_markup or false
+ attr.prefix = attr.no_prefix_markup and attr.prefix or b(attr.prefix)
+ ret = attr.no_value and "" or ret
+
+ s[#s+1] = format("%s%s%s%s%s%s", attr.nl, attr.indent,
+ attr.prefix, attr.prefix_sep, no_ASS(ret), attr.suffix)
+ return true
+end
+
+
+local function append_perfdata(s, dedicated_page)
+ local vo_p = mp.get_property_native("vo-passes")
+ if not vo_p then
+ return
+ end
+
+ local ds = mp.get_property_bool("display-sync-active", false)
+ local target_fps = ds and mp.get_property_number("display-fps", 0)
+ or mp.get_property_number(compat("container-fps"), 0)
+ if target_fps > 0 then target_fps = 1 / target_fps * 1e9 end
+
+ -- Sums of all last/avg/peak values
+ local last_s, avg_s, peak_s = {}, {}, {}
+ for frame, data in pairs(vo_p) do
+ last_s[frame], avg_s[frame], peak_s[frame] = 0, 0, 0
+ for _, pass in ipairs(data) do
+ last_s[frame] = last_s[frame] + pass["last"]
+ avg_s[frame] = avg_s[frame] + pass["avg"]
+ peak_s[frame] = peak_s[frame] + pass["peak"]
+ end
+ end
+
+ -- Pretty print measured time
+ local function pp(i)
+ -- rescale to microseconds for a saner display
+ return format("%05d", i / 1000)
+ end
+
+ -- Format n/m with a font weight based on the ratio
+ local function p(n, m)
+ local i = 0
+ if m > 0 then
+ i = tonumber(n) / m
+ end
+ -- Calculate font weight. 100 is minimum, 400 is normal, 700 bold, 900 is max
+ local w = (700 * math.sqrt(i)) + 200
+ return format("{\\b%d}%02d%%{\\b0}", w, i * 100)
+ end
+
+ s[#s+1] = format("%s%s%s%s{\\fs%s}%s{\\fs%s}",
+ dedicated_page and "" or o.nl, dedicated_page and "" or o.indent,
+ b("Frame Timings:"), o.prefix_sep, o.font_size * 0.66,
+ "(last/average/peak μs)", o.font_size)
+
+ for frame, data in pairs(vo_p) do
+ local f = "%s%s%s{\\fn%s}%s / %s / %s %s%s{\\fn%s}%s%s%s"
+
+ if dedicated_page then
+ s[#s+1] = format("%s%s%s:", o.nl, o.indent,
+ b(frame:gsub("^%l", string.upper)))
+
+ for _, pass in ipairs(data) do
+ s[#s+1] = format(f, o.nl, o.indent, o.indent,
+ o.font_mono, pp(pass["last"]),
+ pp(pass["avg"]), pp(pass["peak"]),
+ o.prefix_sep .. o.prefix_sep, p(pass["last"], last_s[frame]),
+ o.font, o.prefix_sep, o.prefix_sep, pass["desc"])
+
+ if o.plot_perfdata and o.use_ass then
+ s[#s+1] = generate_graph(pass["samples"], pass["count"],
+ pass["count"], pass["peak"],
+ pass["avg"], 0.9, 0.25)
+ end
+ end
+
+ -- Print sum of timing values as "Total"
+ s[#s+1] = format(f, o.nl, o.indent, o.indent,
+ o.font_mono, pp(last_s[frame]),
+ pp(avg_s[frame]), pp(peak_s[frame]), "", "", o.font,
+ o.prefix_sep, o.prefix_sep, b("Total"))
+ else
+ -- for the simplified view, we just print the sum of each pass
+ s[#s+1] = format(f, o.nl, o.indent, o.indent, o.font_mono,
+ pp(last_s[frame]), pp(avg_s[frame]), pp(peak_s[frame]),
+ "", "", o.font, o.prefix_sep, o.prefix_sep,
+ frame:gsub("^%l", string.upper))
+ end
+ end
+end
+
+
+local function append_display_sync(s)
+ if not mp.get_property_bool("display-sync-active", false) then
+ return
+ end
+
+ local vspeed = append_property(s, "video-speed-correction", {prefix="DS:"})
+ if vspeed then
+ append_property(s, "audio-speed-correction",
+ {prefix="/", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true})
+ else
+ append_property(s, "audio-speed-correction",
+ {prefix="DS:" .. o.prefix_sep .. " - / ", prefix_sep=""})
+ end
+
+ append_property(s, "mistimed-frame-count", {prefix="Mistimed:", nl=""})
+ append_property(s, "vo-delayed-frame-count", {prefix="Delayed:", nl=""})
+
+ -- As we need to plot some graphs we print jitter and ratio on their own lines
+ if not display_timer.oneshot and (o.plot_vsync_ratio or o.plot_vsync_jitter) and o.use_ass then
+ local ratio_graph = ""
+ local jitter_graph = ""
+ if o.plot_vsync_ratio then
+ ratio_graph = generate_graph(vsratio_buf, vsratio_buf.pos, vsratio_buf.len, vsratio_buf.max, nil, 0.8, 1)
+ end
+ if o.plot_vsync_jitter then
+ jitter_graph = generate_graph(vsjitter_buf, vsjitter_buf.pos, vsjitter_buf.len, vsjitter_buf.max, nil, 0.8, 1)
+ end
+ append_property(s, "vsync-ratio", {prefix="VSync Ratio:", suffix=o.prefix_sep .. ratio_graph})
+ append_property(s, "vsync-jitter", {prefix="VSync Jitter:", suffix=o.prefix_sep .. jitter_graph})
+ else
+ -- Since no graph is needed we can print ratio/jitter on the same line and save some space
+ local vratio = append_property(s, "vsync-ratio", {prefix="VSync Ratio:"})
+ append_property(s, "vsync-jitter", {prefix="VSync Jitter:", nl="" or o.nl})
+ end
+end
+
+
+local function append_filters(s, prop, prefix)
+ local length = 0
+ local filters = {}
+
+ for _,f in ipairs(mp.get_property_native(prop, {})) do
+ local n = f.name
+ if f.enabled ~= nil and not f.enabled then
+ n = n .. " (disabled)"
+ end
+
+ local p = {}
+ for key,value in pairs(f.params) do
+ p[#p+1] = key .. "=" .. value
+ end
+ if #p > 0 then
+ p = " [" .. table.concat(p, " ") .. "]"
+ else
+ p = ""
+ end
+
+ length = length + n:len() + p:len()
+ filters[#filters+1] = no_ASS(n) .. it(no_ASS(p))
+ end
+
+ if #filters > 0 then
+ local ret
+ if length < o.filter_params_max_length then
+ ret = table.concat(filters, ", ")
+ else
+ local sep = o.nl .. o.indent .. o.indent
+ ret = sep .. table.concat(filters, sep)
+ end
+ s[#s+1] = o.nl .. o.indent .. b(prefix) .. o.prefix_sep .. ret
+ end
+end
+
+
+local function add_header(s)
+ s[#s+1] = text_style()
+end
+
+
+local function add_file(s)
+ append_property(s, "filename", {prefix="File:", nl="", indent=""})
+ if not (mp.get_property_osd("filename") == mp.get_property_osd("media-title")) then
+ append_property(s, "media-title", {prefix="Title:"})
+ end
+
+ local ch_index = mp.get_property_number("chapter")
+ if ch_index and ch_index >= 0 then
+ append_property(s, "chapter-list/" .. tostring(ch_index) .. "/title", {prefix="Chapter:"})
+ append_property(s, "chapter-list/count",
+ {prefix="(" .. tostring(ch_index + 1) .. "/", suffix=")", nl="",
+ indent=" ", prefix_sep=" ", no_prefix_markup=true})
+ end
+ if append_property(s, "cache-used", {prefix="Cache:"}) then
+ append_property(s, "demuxer-cache-duration",
+ {prefix="+", suffix=" sec", nl="", indent=o.prefix_sep,
+ prefix_sep="", no_prefix_markup=true})
+ append_property(s, "cache-speed",
+ {prefix="", suffix="", nl="", indent=o.prefix_sep,
+ prefix_sep="", no_prefix_markup=true})
+ end
+ append_property(s, "file-size", {prefix="Size:"})
+end
+
+
+local function add_video(s)
+ if not has_video() then
+ return
+ end
+
+ if append_property(s, "video-codec", {prefix=o.nl .. o.nl .. "Video:", nl="", indent=""}) then
+ append_property(s, "hwdec-current", {prefix="(hwdec:", nl="", indent=" ",
+ no_prefix_markup=true, suffix=")"}, {no=true, [""]=true})
+ end
+ append_property(s, "avsync", {prefix="A-V:"})
+ if append_property(s, compat("decoder-frame-drop-count"),
+ {prefix="Dropped Frames:", suffix=" (decoder)"}) then
+ append_property(s, compat("frame-drop-count"), {suffix=" (output)", nl="", indent=""})
+ end
+ if append_property(s, "display-fps", {prefix="Display FPS:", suffix=" (specified)"}) then
+ append_property(s, "estimated-display-fps",
+ {suffix=" (estimated)", nl="", indent=""})
+ else
+ append_property(s, "estimated-display-fps",
+ {prefix="Display FPS:", suffix=" (estimated)"})
+ end
+ if append_property(s, compat("container-fps"), {prefix="FPS:", suffix=" (specified)"}) then
+ append_property(s, "estimated-vf-fps",
+ {suffix=" (estimated)", nl="", indent=""})
+ else
+ append_property(s, "estimated-vf-fps",
+ {prefix="FPS:", suffix=" (estimated)"})
+ end
+
+ append_display_sync(s)
+ append_perfdata(s, o.print_perfdata_passes)
+
+ if append_property(s, "video-params/w", {prefix="Native Resolution:"}) then
+ append_property(s, "video-params/h",
+ {prefix="x", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true})
+ end
+ append_property(s, "window-scale", {prefix="Window Scale:"})
+ append_property(s, "video-params/aspect", {prefix="Aspect Ratio:"})
+ append_property(s, "video-params/pixelformat", {prefix="Pixel Format:"})
+
+ -- Group these together to save vertical space
+ local prim = append_property(s, "video-params/primaries", {prefix="Primaries:"})
+ local cmat = append_property(s, "video-params/colormatrix",
+ {prefix="Colormatrix:", nl=prim and "" or o.nl})
+ append_property(s, "video-params/colorlevels", {prefix="Levels:", nl=cmat and "" or o.nl})
+
+ -- Append HDR metadata conditionally (only when present and interesting)
+ local hdrpeak = mp.get_property_number("video-params/sig-peak", 0)
+ local hdrinfo = ""
+ if hdrpeak > 1 then
+ hdrinfo = " (HDR peak: " .. hdrpeak .. ")"
+ end
+
+ append_property(s, "video-params/gamma", {prefix="Gamma:", suffix=hdrinfo})
+ append_property(s, "packet-video-bitrate", {prefix="Bitrate:", suffix=" kbps"})
+ append_filters(s, "vf", "Filters:")
+end
+
+
+local function add_audio(s)
+ if not has_audio() then
+ return
+ end
+
+ append_property(s, "audio-codec", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""})
+ append_property(s, "audio-params/samplerate", {prefix="Sample Rate:", suffix=" Hz"})
+ append_property(s, "audio-params/channel-count", {prefix="Channels:"})
+ append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"})
+ append_filters(s, "af", "Filters:")
+end
+
+
+-- Determine whether ASS formatting shall/can be used and set formatting sequences
+local function eval_ass_formatting()
+ o.use_ass = o.ass_formatting and has_vo_window()
+ if o.use_ass then
+ o.nl = o.ass_nl
+ o.indent = o.ass_indent
+ o.prefix_sep = o.ass_prefix_sep
+ o.b1 = o.ass_b1
+ o.b0 = o.ass_b0
+ o.it1 = o.ass_it1
+ o.it0 = o.ass_it0
+ else
+ o.nl = o.no_ass_nl
+ o.indent = o.no_ass_indent
+ o.prefix_sep = o.no_ass_prefix_sep
+ if not has_ansi() then
+ o.b1 = ""
+ o.b0 = ""
+ o.it1 = ""
+ o.it0 = ""
+ else
+ o.b1 = o.no_ass_b1
+ o.b0 = o.no_ass_b0
+ o.it1 = o.no_ass_it1
+ o.it0 = o.no_ass_it0
+ end
+ end
+end
+
+
+-- Returns an ASS string with "normal" stats
+local function default_stats()
+ local stats = {}
+ eval_ass_formatting()
+ add_header(stats)
+ add_file(stats)
+ add_video(stats)
+ add_audio(stats)
+ return table.concat(stats)
+end
+
+
+-- Returns an ASS string with extended VO stats
+local function vo_stats()
+ local stats = {}
+ eval_ass_formatting()
+ add_header(stats)
+ append_perfdata(stats, true)
+ return table.concat(stats)
+end
+
+
+-- Returns an ASS string with stats about filters/profiles/shaders
+local function filter_stats()
+ return "coming soon"
+end
+
+
+-- Current page and <page key>:<page function> mapping
+curr_page = o.key_page_1
+pages = {
+ [o.key_page_1] = { f = default_stats, desc = "Default" },
+ [o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings" },
+ --[o.key_page_3] = { f = filter_stats, desc = "Dummy" },
+}
+
+
+-- Returns a function to record vsratio/jitter with the specified `skip` value
+local function record_data(skip)
+ init_buffers()
+ skip = max(skip, 0)
+ local i = skip
+ return function()
+ if i < skip then
+ i = i + 1
+ return
+ else
+ i = 0
+ end
+
+ if o.plot_vsync_jitter then
+ local r = mp.get_property_number("vsync-jitter", nil)
+ if r then
+ vsjitter_buf.pos = (vsjitter_buf.pos % vsjitter_buf.len) + 1
+ vsjitter_buf[vsjitter_buf.pos] = r
+ vsjitter_buf.max = max(vsjitter_buf.max, r)
+ end
+ end
+
+ if o.plot_vsync_ratio then
+ local r = mp.get_property_number("vsync-ratio", nil)
+ if r then
+ vsratio_buf.pos = (vsratio_buf.pos % vsratio_buf.len) + 1
+ vsratio_buf[vsratio_buf.pos] = r
+ vsratio_buf.max = max(vsratio_buf.max, r)
+ end
+ end
+ end
+end
+
+
+-- Call the function for `page` and print it to OSD
+local function print_page(page)
+ if o.persistent_overlay then
+ mp.set_osd_ass(0, 0, pages[page].f())
+ else
+ mp.osd_message(pages[page].f(), display_timer.oneshot and o.duration or o.redraw_delay + 1)
+ end
+end
+
+
+local function clear_screen()
+ if o.persistent_overlay then mp.set_osd_ass(0, 0, "") else mp.osd_message("", 0) end
+end
+
+
+-- Add keybindings for every page
+local function add_page_bindings()
+ local function a(k)
+ return function()
+ curr_page = k
+ print_page(k)
+ if display_timer.oneshot then display_timer:kill() ; display_timer:resume() end
+ end
+ end
+ for k, _ in pairs(pages) do
+ mp.add_forced_key_binding(k, k, a(k), {repeatable=true})
+ end
+end
+
+
+-- Remove keybindings for every page
+local function remove_page_bindings()
+ for k, _ in pairs(pages) do
+ mp.remove_key_binding(k)
+ end
+end
+
+
+local function process_key_binding(oneshot)
+ -- Stats are already being displayed
+ if display_timer:is_enabled() then
+ -- Previous and current keys were oneshot -> restart timer
+ if display_timer.oneshot and oneshot then
+ display_timer:kill()
+ print_page(curr_page)
+ display_timer:resume()
+ -- Previous and current keys were toggling -> end toggling
+ elseif not display_timer.oneshot and not oneshot then
+ display_timer:kill()
+ clear_screen()
+ remove_page_bindings()
+ if recorder then
+ mp.unregister_event(recorder)
+ recorder = nil
+ end
+ end
+ -- No stats are being displayed yet
+ else
+ if not oneshot and (o.plot_vsync_jitter or o.plot_vsync_ratio) then
+ recorder = record_data(o.skip_frames)
+ mp.register_event("tick", recorder)
+ end
+ display_timer:kill()
+ display_timer.oneshot = oneshot
+ display_timer.timeout = oneshot and o.duration or o.redraw_delay
+ add_page_bindings()
+ print_page(curr_page)
+ display_timer:resume()
+ end
+end
+
+
+-- Create the timer used for redrawing (toggling) or clearing the screen (oneshot)
+-- The duration here is not important and always set in process_key_binding()
+display_timer = mp.add_periodic_timer(o.duration,
+ function()
+ if display_timer.oneshot then
+ display_timer:kill() ; clear_screen() ; remove_page_bindings()
+ else
+ print_page(curr_page)
+ end
+ end)
+display_timer:kill()
+
+-- Single invocation key binding
+mp.add_key_binding(o.key_oneshot, "display-stats", function() process_key_binding(true) end,
+ {repeatable=true})
+
+-- Toggling key binding
+mp.add_key_binding(o.key_toggle, "display-stats-toggle", function() process_key_binding(false) end,
+ {repeatable=false})
+
+-- Single invocation bindings without key, can be used in input.conf to create
+-- bindings for a specific page: "e script-binding stats/display-page-2"
+for k, _ in pairs(pages) do
+ mp.add_key_binding(nil, "display-page-" .. k, function() process_key_binding(true) end,
+ {repeatable=true})
+end
+
+-- Reprint stats immediately when VO was reconfigured, only when toggled
+mp.register_event("video-reconfig",
+ function()
+ if display_timer:is_enabled() then
+ print_page(curr_page)
+ end
+ end)
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index 82679a2..8aef3e4 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -454,15 +454,14 @@ mp.add_hook("on_load", 10, function ()
msg.verbose("Playlist with single entry detected.")
add_single_video(json.entries[1])
else
-
- local playlist = "#EXTM3U\n"
+ local playlist = {"#EXTM3U"}
for i, entry in pairs(json.entries) do
local site = entry.url
local title = entry.title
if not (title == nil) then
title = string.gsub(title, '%s+', ' ')
- playlist = playlist .. "#EXTINF:0," .. title .. "\n"
+ table.insert(playlist, "#EXTINF:0," .. title)
end
-- some extractors will still return the full info for
@@ -475,10 +474,14 @@ mp.add_hook("on_load", 10, function ()
site = entry["webpage_url"]
end
- playlist = playlist .. "ytdl://" .. site .. "\n"
+ if not (site:find("https?://") == 1) then
+ site = "ytdl://" .. site
+ end
+ table.insert(playlist, site)
+
end
- mp.set_property("stream-open-filename", "memory://" .. playlist)
+ mp.set_property("stream-open-filename", "memory://" .. table.concat(playlist, "\n"))
end
else -- probably a video
diff --git a/player/main.c b/player/main.c
index 56a4f1d..2a3b6c6 100644
--- a/player/main.c
+++ b/player/main.c
@@ -13,8 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#include <stdio.h>
@@ -149,11 +147,11 @@ void mp_print_version(struct mp_log *log, int always)
mp_msg(log, v, "\n");
// Only in verbose mode.
if (!always) {
-#if HAVE_GPL
- // Possibly GPL due to 0810e42750fb2e2e0d602388cef1b8ea8015d935.
mp_msg(log, MSGL_V, "Configuration: " CONFIGURATION "\n");
-#endif
mp_msg(log, MSGL_V, "List of enabled features: %s\n", FULLCONFIG);
+ #ifdef NDEBUGs
+ mp_msg(log, MSGL_V, "Built with NDEBUG.\n");
+ #endif
}
}
@@ -297,21 +295,6 @@ static bool handle_help_options(struct MPContext *mpctx)
return false;
}
-static void handle_deprecated_options(struct MPContext *mpctx)
-{
- struct MPOpts *opts = mpctx->opts;
- struct m_obj_settings *vo = opts->vo->video_driver_list;
- if (vo && vo->name && strcmp(vo->name, "opengl-hq") == 0) {
- MP_WARN(mpctx,
- "--vo=opengl-hq is deprecated! Use --profile=opengl-hq instead.\n");
- // Fudge it. This will replace the --vo option too, which is why we
- // unset/safe it, and later restore it.
- talloc_free(vo->name);
- vo->name = talloc_strdup(NULL, "opengl");
- m_config_set_profile(mpctx->mconfig, "opengl-hq", 0);
- }
-}
-
static int cfg_include(void *ctx, char *filename, int flags)
{
struct MPContext *mpctx = ctx;
@@ -445,8 +428,6 @@ int mp_initialize(struct MPContext *mpctx, char **options)
if (handle_help_options(mpctx))
return -2;
- handle_deprecated_options(mpctx);
-
if (!print_libav_versions(mp_null_log, 0)) {
// Using mismatched libraries can be legitimate, but even then it's
// a bad idea. We don't acknowledge its usefulness and stability.
diff --git a/player/misc.c b/player/misc.c
index 87e6521..0284479 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -48,6 +48,8 @@
double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t)
{
double length = get_time_length(mpctx);
+ // declaration up here because of C grammar quirk
+ double chapter_start_pts;
switch (t.type) {
case REL_TIME_ABSOLUTE:
return t.pos;
@@ -64,8 +66,20 @@ double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t)
return length * (t.pos / 100.0);
break;
case REL_TIME_CHAPTER:
- if (chapter_start_time(mpctx, t.pos) != MP_NOPTS_VALUE)
- return chapter_start_time(mpctx, t.pos);
+ chapter_start_pts = chapter_start_time(mpctx, t.pos);
+ if (chapter_start_pts != MP_NOPTS_VALUE){
+ /*
+ * rel_time_to_abs always returns rebased timetamps,
+ * even with --rebase-start-time=no. (See the above two
+ * cases.) chapter_start_time values are not rebased without
+ * --rebase-start-time=yes, so we need to rebase them
+ * here to be consistent with the rest of rel_time_to_abs.
+ */
+ if (mpctx->demuxer && !mpctx->opts->rebase_start_time){
+ chapter_start_pts -= mpctx->demuxer->start_time;
+ }
+ return chapter_start_pts;
+ }
break;
}
return MP_NOPTS_VALUE;
@@ -77,12 +91,13 @@ double get_play_end_pts(struct MPContext *mpctx)
double end = MP_NOPTS_VALUE;
if (opts->play_end.type) {
end = rel_time_to_abs(mpctx, opts->play_end);
- } else if (opts->play_length.type) {
- double start = rel_time_to_abs(mpctx, opts->play_start);
+ }
+ if (opts->play_length.type) {
+ double start = get_play_start_pts(mpctx);
if (start == MP_NOPTS_VALUE)
start = 0;
double length = rel_time_to_abs(mpctx, opts->play_length);
- if (length != MP_NOPTS_VALUE)
+ if (length != MP_NOPTS_VALUE && (end == MP_NOPTS_VALUE || start + length < end))
end = start + length;
}
if (opts->chapterrange[1] > 0) {
@@ -90,8 +105,11 @@ double get_play_end_pts(struct MPContext *mpctx)
if (cend != MP_NOPTS_VALUE && (end == MP_NOPTS_VALUE || cend < end))
end = cend;
}
+ // even though MP_NOPTS_VALUE is currently negative
+ // it doesn't necessarily have to remain that way
+ double ab_loop_start_time = get_ab_loop_start_time(mpctx);
if (mpctx->ab_loop_clip && opts->ab_loop[1] != MP_NOPTS_VALUE &&
- opts->ab_loop[1] > opts->ab_loop[0])
+ (ab_loop_start_time == MP_NOPTS_VALUE || opts->ab_loop[1] > ab_loop_start_time))
{
if (end == MP_NOPTS_VALUE || end > opts->ab_loop[1])
end = opts->ab_loop[1];
@@ -99,6 +117,64 @@ double get_play_end_pts(struct MPContext *mpctx)
return end;
}
+/**
+ * Get the rebased PTS for which playback should start.
+ * The order of priority is as follows:
+ * 1. --start, if set.
+ * 2. The start chapter, if set.
+ * 3. MP_NOPTS_VALUE.
+ * If unspecified, return MP_NOPTS_VALUE.
+ * Does not return zero unless the start time is explicitly set to zero.
+ */
+double get_play_start_pts(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ double play_start_pts = rel_time_to_abs(mpctx, opts->play_start);
+ if (play_start_pts == MP_NOPTS_VALUE && opts->chapterrange[0] > 0) {
+ double chapter_start_pts = chapter_start_time(mpctx, opts->chapterrange[0] - 1);
+ if (chapter_start_pts != MP_NOPTS_VALUE) {
+ /*
+ * get_play_start_pts always returns rebased timetamps,
+ * even with --rebase-start-time=no. chapter_start_time
+ * values are not rebased without --rebase-start-time=yes,
+ * so we need to rebase them here to be consistent with
+ * the rest of get_play_start_pts.
+ */
+ if (mpctx->demuxer && !mpctx->opts->rebase_start_time){
+ chapter_start_pts -= mpctx->demuxer->start_time;
+ }
+ play_start_pts = chapter_start_pts;
+ }
+ }
+ return play_start_pts;
+}
+
+/**
+ * Get the time that an ab-loop seek should seek to.
+ * The order of priority is as follows:
+ * 1. --ab-loop-a, if set.
+ * 2. The Playback Start PTS, if set.
+ * 3. MP_NOPTS_VALUE.
+ * If unspecified, return MP_NOPTS_VALUE.
+ * Does not return zero unless the start time is explicitly set to zero.
+ */
+double get_ab_loop_start_time(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ double ab_loop_start_time;
+ if (opts->ab_loop[0] != MP_NOPTS_VALUE) {
+ ab_loop_start_time = opts->ab_loop[0];
+ } else {
+ /*
+ * There is no check for MP_NOPTS_VALUE here
+ * because that's exactly what we want to return
+ * if get_play_start_pts comes up empty here.
+ */
+ ab_loop_start_time = get_play_start_pts(mpctx);
+ }
+ return ab_loop_start_time;
+}
+
double get_track_seek_offset(struct MPContext *mpctx, struct track *track)
{
struct MPOpts *opts = mpctx->opts;
diff --git a/player/osd.c b/player/osd.c
index a95a6c5..e9fda35 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -13,8 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#include <stddef.h>
@@ -57,14 +55,6 @@ static void sadd_hhmmssff(char **buf, double time, bool fractions)
talloc_free(s);
}
-// If time unknown (MP_NOPTS_VALUE), use 0 instead.
-static void sadd_hhmmssff_u(char **buf, double time, bool fractions)
-{
- if (time == MP_NOPTS_VALUE)
- time = 0;
- sadd_hhmmssff(buf, time, fractions);
-}
-
static void sadd_percentage(char **buf, int percent) {
if (percent >= 0)
*buf = talloc_asprintf_append(*buf, " (%d%%)", percent);
@@ -207,13 +197,9 @@ static void term_osd_print_status_lazy(struct MPContext *mpctx)
saddf(&line, ": ");
// Playback position
- sadd_hhmmssff_u(&line, get_playback_time(mpctx), mpctx->opts->osd_fractions);
-
- double len = get_time_length(mpctx);
- if (len >= 0) {
- saddf(&line, " / ");
- sadd_hhmmssff(&line, len, mpctx->opts->osd_fractions);
- }
+ sadd_hhmmssff(&line, get_playback_time(mpctx), mpctx->opts->osd_fractions);
+ saddf(&line, " / ");
+ sadd_hhmmssff(&line, get_time_length(mpctx), mpctx->opts->osd_fractions);
sadd_percentage(&line, get_percent_pos(mpctx));
@@ -275,11 +261,12 @@ static void term_osd_print_status_lazy(struct MPContext *mpctx)
} else {
saddf(&line, "%2ds", (int)s.ts_duration);
}
- if (info.size > 0) {
- if (info.fill >= 1024 * 1024) {
- saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024));
+ int64_t cache_size = s.fw_bytes + info.fill;
+ if (cache_size > 0) {
+ if (cache_size >= 1024 * 1024) {
+ saddf(&line, "+%lldMB", (long long)(cache_size / 1024 / 1024));
} else {
- saddf(&line, "+%lldKB", (long long)(info.fill / 1024));
+ saddf(&line, "+%lldKB", (long long)(cache_size / 1024));
}
}
}
@@ -370,9 +357,13 @@ void set_osd_bar_chapters(struct MPContext *mpctx, int type)
mpctx->osd_progbar.num_stops = 0;
double len = get_time_length(mpctx);
if (len > 0) {
- if (opts->ab_loop[0] != MP_NOPTS_VALUE) {
+ double ab_loop_start_time = get_ab_loop_start_time(mpctx);
+ if (opts->ab_loop[0] != MP_NOPTS_VALUE ||
+ (ab_loop_start_time != MP_NOPTS_VALUE &&
+ opts->ab_loop[1] != MP_NOPTS_VALUE))
+ {
MP_TARRAY_APPEND(mpctx, mpctx->osd_progbar.stops,
- mpctx->osd_progbar.num_stops, opts->ab_loop[0] / len);
+ mpctx->osd_progbar.num_stops, ab_loop_start_time / len);
}
if (opts->ab_loop[1] != MP_NOPTS_VALUE) {
MP_TARRAY_APPEND(mpctx, mpctx->osd_progbar.stops,
@@ -442,18 +433,12 @@ static void sadd_osd_status(char **buffer, struct MPContext *mpctx, int level)
*buffer = talloc_strdup_append(*buffer, text);
talloc_free(text);
} else {
- sadd_hhmmssff_u(buffer, get_playback_time(mpctx), fractions);
-#if HAVE_GPL
- // Potentially GPL due to 8d190244d21a4d40bb9e8f7d51aa09ca1888de09.
+ sadd_hhmmssff(buffer, get_playback_time(mpctx), fractions);
if (level == 3) {
- double len = get_time_length(mpctx);
- if (len >= 0) {
- saddf(buffer, " / ");
- sadd_hhmmssff(buffer, len, fractions);
- }
+ saddf(buffer, " / ");
+ sadd_hhmmssff(buffer, get_time_length(mpctx), fractions);
sadd_percentage(buffer, get_percent_pos(mpctx));
}
-#endif
}
}
}
diff --git a/player/playloop.c b/player/playloop.c
index c9bc910..245b492 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -39,7 +39,6 @@
#include "osdep/timer.h"
#include "audio/decode/dec_audio.h"
-#include "audio/filter/af.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "stream/stream.h"
@@ -254,12 +253,6 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
if (!mpctx->demuxer || seek.type == MPSEEK_NONE || seek.amount == MP_NOPTS_VALUE)
return;
- if (!mpctx->demuxer->seekable) {
- MP_ERR(mpctx, "Cannot seek in this file.\n");
- MP_ERR(mpctx, "You can forcibly enable it with '--force-seekable=yes'.\n");
- return;
- }
-
bool hr_seek_very_exact = seek.exact == MPSEEK_VERY_EXACT;
double current_time = get_current_time(mpctx);
if (current_time == MP_NOPTS_VALUE && seek.type == MPSEEK_RELATIVE)
@@ -278,14 +271,13 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
hr_seek_very_exact = true;
break;
case MPSEEK_RELATIVE:
- demux_flags = seek.amount > 0 ? SEEK_FORWARD : SEEK_BACKWARD;
+ demux_flags = seek.amount > 0 ? SEEK_FORWARD : 0;
seek_pts = current_time + seek.amount;
break;
case MPSEEK_FACTOR: ;
double len = get_time_length(mpctx);
if (len >= 0)
seek_pts = seek.amount * len;
- demux_flags = SEEK_BACKWARD;
break;
default: abort();
}
@@ -324,10 +316,19 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
hr_seek_offset = MPMAX(hr_seek_offset, -offset);
}
demux_pts -= hr_seek_offset;
- demux_flags = (demux_flags | SEEK_HR | SEEK_BACKWARD) & ~SEEK_FORWARD;
+ demux_flags = (demux_flags | SEEK_HR) & ~SEEK_FORWARD;
}
- demux_seek(mpctx->demuxer, demux_pts, demux_flags);
+ if (!mpctx->demuxer->seekable)
+ demux_flags |= SEEK_CACHED;
+
+ if (!demux_seek(mpctx->demuxer, demux_pts, demux_flags)) {
+ if (!mpctx->demuxer->seekable) {
+ MP_ERR(mpctx, "Cannot seek in this file.\n");
+ MP_ERR(mpctx, "You can force it with '--force-seekable=yes'.\n");
+ }
+ return;
+ }
// Seek external, extra files too:
for (int t = 0; t < mpctx->num_tracks; t++) {
@@ -440,11 +441,11 @@ void execute_queued_seek(struct MPContext *mpctx)
}
}
-// -1 if unknown
+// NOPTS (i.e. <0) if unknown
double get_time_length(struct MPContext *mpctx)
{
struct demuxer *demuxer = mpctx->demuxer;
- return demuxer ? demuxer->duration : -1;
+ return demuxer && demuxer->duration >= 0 ? demuxer->duration : MP_NOPTS_VALUE;
}
double get_current_time(struct MPContext *mpctx)
@@ -484,7 +485,7 @@ double get_current_pos_ratio(struct MPContext *mpctx, bool use_range)
double start = 0;
double len = get_time_length(mpctx);
if (use_range) {
- double startpos = rel_time_to_abs(mpctx, mpctx->opts->play_start);
+ double startpos = get_play_start_pts(mpctx);
double endpos = get_play_end_pts(mpctx);
if (endpos == MP_NOPTS_VALUE || endpos > MPMAX(0, len))
endpos = MPMAX(0, len);
@@ -685,21 +686,6 @@ int get_cache_buffering_percentage(struct MPContext *mpctx)
return mpctx->demuxer ? mpctx->cache_buffer : -1;
}
-static void handle_heartbeat_cmd(struct MPContext *mpctx)
-{
-#if !HAVE_UWP
- struct MPOpts *opts = mpctx->opts;
- if (opts->heartbeat_cmd && !mpctx->paused && mpctx->video_out) {
- double now = mp_time_sec();
- if (mpctx->next_heartbeat <= now) {
- mpctx->next_heartbeat = now + opts->heartbeat_interval;
- (void)system(opts->heartbeat_cmd);
- }
- mp_set_timeout(mpctx, mpctx->next_heartbeat - now);
- }
-#endif
-}
-
static void handle_cursor_autohide(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
@@ -785,20 +771,29 @@ static void handle_loop_file(struct MPContext *mpctx)
// Assumes execute_queued_seek() happens before next audio/video is
// attempted to be decoded or filtered.
mpctx->stop_play = KEEP_PLAYING;
- double start = 0;
- if (opts->ab_loop[0] != MP_NOPTS_VALUE)
- start = opts->ab_loop[0];
+ double start = get_ab_loop_start_time(mpctx);
+ if (start == MP_NOPTS_VALUE)
+ start = 0;
mark_seek(mpctx);
queue_seek(mpctx, MPSEEK_ABSOLUTE, start, MPSEEK_EXACT,
MPSEEK_FLAG_NOFLUSH);
}
- if (opts->loop_file && mpctx->stop_play == AT_END_OF_FILE) {
- mpctx->stop_play = KEEP_PLAYING;
- set_osd_function(mpctx, OSD_FFW);
- queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, MPSEEK_FLAG_NOFLUSH);
- if (opts->loop_file > 0)
- opts->loop_file--;
+ // Do not attempt to loop-file if --ab-loop is active.
+ else if (opts->loop_file && mpctx->stop_play == AT_END_OF_FILE) {
+ double play_start_pts = get_play_start_pts(mpctx);
+ if (play_start_pts == MP_NOPTS_VALUE)
+ play_start_pts = 0;
+ double play_end_pts = get_play_end_pts(mpctx);
+ if (play_end_pts == MP_NOPTS_VALUE || play_start_pts < play_end_pts){
+ mpctx->stop_play = KEEP_PLAYING;
+ set_osd_function(mpctx, OSD_FFW);
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, play_start_pts, MPSEEK_EXACT,
+ MPSEEK_FLAG_NOFLUSH);
+ if (opts->loop_file > 0)
+ opts->loop_file--;
+ }
+
}
}
@@ -1089,7 +1084,6 @@ void run_playloop(struct MPContext *mpctx)
handle_cursor_autohide(mpctx);
handle_vo_events(mpctx);
- handle_heartbeat_cmd(mpctx);
handle_command_updates(mpctx);
if (mpctx->lavfi) {
diff --git a/player/screenshot.c b/player/screenshot.c
index 7f79b2c..5234b39 100644
--- a/player/screenshot.c
+++ b/player/screenshot.c
@@ -364,6 +364,11 @@ static char *gen_fname(screenshot_ctx *ctx, const char *file_ext)
talloc_free(t);
}
+ char *full_dir = bstrto0(fname, mp_dirname(fname));
+ if (!mp_path_exists(full_dir)) {
+ mp_mkdirp(full_dir);
+ }
+
if (!mp_path_exists(fname))
return fname;
@@ -404,12 +409,6 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
if (image && (image->fmt.flags & MP_IMGFLAG_HWACCEL)) {
struct mp_image *nimage = mp_image_hw_download(image, NULL);
- if (!nimage && mpctx->vo_chain && mpctx->vo_chain->hwdec_devs) {
- struct mp_hwdec_ctx *ctx =
- hwdec_devices_get_first(mpctx->vo_chain->hwdec_devs);
- if (ctx && ctx->download_image)
- nimage = ctx->download_image(ctx, image, NULL);
- }
talloc_free(image);
image = nimage;
}
diff --git a/player/scripting.c b/player/scripting.c
index 5651950..38e8809 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -140,7 +140,7 @@ int mp_load_script(struct MPContext *mpctx, const char *fname)
}
arg->log = mp_client_get_log(arg->client);
- MP_VERBOSE(arg, "Loading %s %s...\n", backend->name, fname);
+ MP_DBG(arg, "Loading %s %s...\n", backend->name, fname);
pthread_t thread;
if (pthread_create(&thread, NULL, script_thread, arg)) {
@@ -150,7 +150,7 @@ int mp_load_script(struct MPContext *mpctx, const char *fname)
}
wait_loaded(mpctx);
- MP_VERBOSE(mpctx, "Done loading %s.\n", fname);
+ MP_DBG(mpctx, "Done loading %s.\n", fname);
return 0;
}
@@ -220,6 +220,7 @@ void mp_load_builtin_scripts(struct MPContext *mpctx)
{
load_builtin_script(mpctx, mpctx->opts->lua_load_osc, "@osc.lua");
load_builtin_script(mpctx, mpctx->opts->lua_load_ytdl, "@ytdl_hook.lua");
+ load_builtin_script(mpctx, mpctx->opts->lua_load_stats, "@stats.lua");
}
void mp_load_scripts(struct MPContext *mpctx)
@@ -253,7 +254,7 @@ typedef int (*mpv_open_cplugin)(mpv_handle *handle);
static int load_cplugin(struct mpv_handle *client, const char *fname)
{
- int r = -1;
+ MPContext *ctx = mp_client_get_core(client);
void *lib = dlopen(fname, RTLD_NOW | RTLD_LOCAL);
if (!lib)
goto error;
@@ -262,9 +263,12 @@ static int load_cplugin(struct mpv_handle *client, const char *fname)
mpv_open_cplugin sym = (mpv_open_cplugin)dlsym(lib, MPV_DLOPEN_FN);
if (!sym)
goto error;
- r = sym(client) ? -1 : 0;
-error:
- return r;
+ return sym(client) ? -1 : 0;
+error: ;
+ char *err = dlerror();
+ if (err)
+ MP_ERR(ctx, "C plugin error: '%s'\n", err);
+ return -1;
}
const struct mp_scripting mp_scripting_cplugin = {
diff --git a/player/video.c b/player/video.c
index e1034c4..ef1423c 100644
--- a/player/video.c
+++ b/player/video.c
@@ -34,6 +34,7 @@
#include "osdep/timer.h"
#include "audio/out/ao.h"
+#include "audio/format.h"
#include "demux/demux.h"
#include "stream/stream.h"
#include "sub/osd.h"
@@ -42,7 +43,6 @@
#include "video/decode/dec_video.h"
#include "video/decode/vd.h"
#include "video/out/vo.h"
-#include "audio/filter/af.h"
#include "audio/decode/dec_audio.h"
#include "core.h"
@@ -116,7 +116,7 @@ static int probe_deint_filters(struct vo_chain *vo_c)
if (check_output_format(vo_c, IMGFMT_D3D11VA) ||
check_output_format(vo_c, IMGFMT_D3D11NV12))
return try_filter(vo_c, "d3d11vpp", VF_DEINTERLACE_LABEL, NULL);
- char *args[] = {"warn", "no", NULL};
+ char *args[] = {"mode", "send_field", "deint", "interlaced", NULL};
return try_filter(vo_c, "yadif", VF_DEINTERLACE_LABEL, args);
}
@@ -129,7 +129,7 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
set_allowed_vo_formats(vo_c);
- char *filters[] = {"autorotate", "autostereo3d", "deinterlace", NULL};
+ char *filters[] = {"autorotate", "deinterlace", NULL};
for (int n = 0; filters[n]; n++) {
struct vf_instance *vf = vf_find_by_label(vo_c->vf, filters[n]);
if (vf)
@@ -144,23 +144,16 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
if (params.rotate) {
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90) || params.rotate % 90) {
// Try to insert a rotation filter.
- char *args[] = {"angle", "auto", "warn", "no", NULL};
+ double angle = params.rotate / 360.0 * M_PI * 2;
+ char *args[] = {"angle", mp_tprintf(30, "%f", angle),
+ "ow", mp_tprintf(30, "rotw(%f)", angle),
+ "oh", mp_tprintf(30, "roth(%f)", angle),
+ NULL};
if (try_filter(vo_c, "rotate", "autorotate", args) < 0)
MP_ERR(vo_c, "Can't insert rotation filter.\n");
}
}
- if (params.stereo_in != params.stereo_out &&
- params.stereo_in > 0 && params.stereo_out >= 0)
- {
- char *to = (char *)MP_STEREO3D_NAME(params.stereo_out);
- if (to) {
- char *args[] = {"in", "auto", "out", to, "warn", "no", NULL, NULL};
- if (try_filter(vo_c, "stereo3d", "autostereo3d", args) < 0)
- MP_ERR(vo_c, "Can't insert 3D conversion filter.\n");
- }
- }
-
if (mpctx->opts->deinterlace)
probe_deint_filters(vo_c);
}
@@ -319,13 +312,14 @@ int init_video_decoder(struct MPContext *mpctx, struct track *track)
d_video->header = track->stream;
d_video->codec = track->stream->codec;
d_video->fps = d_video->header->codec->fps;
- d_video->vo = mpctx->vo_chain->vo;
// Note: at least mpv_opengl_cb_uninit_gl() relies on being able to get
// rid of all references to the VO by destroying the VO chain. Thus,
// decoders not linked to vo_chain must not use the hwdec context.
- if (mpctx->vo_chain)
+ if (mpctx->vo_chain) {
d_video->hwdec_devs = mpctx->vo_chain->hwdec_devs;
+ d_video->vo = mpctx->vo_chain->vo;
+ }
MP_VERBOSE(d_video, "Container reported FPS: %f\n", d_video->fps);
@@ -547,7 +541,7 @@ static int video_filter(struct MPContext *mpctx, bool eof)
vf->initialized = 0;
mp_image_unrefp(&vo_c->input_mpi);
vo_c->input_format = (struct mp_image_params){0};
- MP_VERBOSE(mpctx, "hwdec falback due to filters.\n");
+ MP_VERBOSE(mpctx, "hwdec fallback due to filters.\n");
return VD_PROGRESS; // try again
}
if (vf->initialized < 1) {
@@ -559,6 +553,8 @@ static int video_filter(struct MPContext *mpctx, bool eof)
// If something was decoded, and the filter chain is ready, filter it.
if (!need_vf_reconfig && vo_c->input_mpi) {
+ if (osd_get_render_subs_in_filter(mpctx->osd))
+ update_subtitles(mpctx, vo_c->input_mpi->pts);
vf_filter_frame(vf, vo_c->input_mpi);
vo_c->input_mpi = NULL;
return VD_PROGRESS;
@@ -1076,10 +1072,10 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
double prev_error = mpctx->display_sync_error;
mpctx->display_sync_error += frame_duration - num_vsyncs * vsync;
- MP_DBG(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f/%f)\n",
- mpctx->speed_factor_v, num_vsyncs, adjusted_duration, ratio,
- mpctx->display_sync_error, mpctx->display_sync_error / vsync,
- mpctx->display_sync_error / frame_duration);
+ MP_TRACE(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f/%f)\n",
+ mpctx->speed_factor_v, num_vsyncs, adjusted_duration, ratio,
+ mpctx->display_sync_error, mpctx->display_sync_error / vsync,
+ mpctx->display_sync_error / frame_duration);
MP_STATS(mpctx, "value %f avdiff", av_diff);
@@ -1327,7 +1323,7 @@ void write_video(struct MPContext *mpctx)
osd_set_force_video_pts(mpctx->osd, MP_NOPTS_VALUE);
if (!update_subtitles(mpctx, mpctx->next_frames[0]->pts)) {
- MP_VERBOSE(mpctx, "Video frame delayed due waiting on subtitles.\n");
+ MP_VERBOSE(mpctx, "Video frame delayed due to waiting on subtitles.\n");
return;
}
diff --git a/stream/audio_in.h b/stream/audio_in.h
index 28d7d33..6b714f7 100644
--- a/stream/audio_in.h
+++ b/stream/audio_in.h
@@ -24,6 +24,10 @@
#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
struct mp_log;
#if HAVE_ALSA
diff --git a/stream/cache.c b/stream/cache.c
index a458dd1..d91ae73 100644
--- a/stream/cache.c
+++ b/stream/cache.c
@@ -4,7 +4,7 @@
* mpv 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 3 of the License, or (at your option) any later version.
+ * version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -13,13 +13,6 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * The parts making this file LGPL v3 (instead of v2.1 or later) are:
- * 84ec57750883 remove unused cache-prefill and create cache-seek-min that...
- * 9b0d8c680f63 cache min fill adjustment, based on patch by Jeremy Huddleston
- * (iive agreed to LGPL v3+ only. Jeremy agreed to LGPL v2.1 or later.)
- * Once these changes are not relevant to for copyright anymore (e.g. because
- * they have been removed), this file will change to LGPLv2.1+.
*/
// Time in seconds the main thread waits for the cache thread. On wakeups, the
@@ -86,10 +79,10 @@ const struct m_sub_options stream_cache_conf = {
.size = sizeof(struct mp_cache_opts),
.defaults = &(const struct mp_cache_opts){
.size = -1,
- .def_size = 75000,
+ .def_size = 10000,
.initial = 0,
.seek_min = 500,
- .back_buffer = 75000,
+ .back_buffer = 10000,
.file_max = 1024 * 1024,
},
};
@@ -116,6 +109,7 @@ struct priv {
// Owned by the cache thread
stream_t *stream; // "real" stream, used to read from the source media
+ int64_t bytes_until_wakeup; // wakeup cache thread after this many bytes
// All the following members are shared between the threads.
// You must lock the mutex to access them.
@@ -141,6 +135,8 @@ struct priv {
int64_t eof_pos;
+ bool read_seek_failed; // let a read fail because an async seek failed
+
int control; // requested STREAM_CTRL_... or CACHE_CTRL_...
void *control_arg; // temporary for executing STREAM_CTRLs
int control_res;
@@ -252,6 +248,8 @@ static bool cache_update_stream_position(struct priv *s)
{
int64_t read = s->read_filepos;
+ s->read_seek_failed = false;
+
if (needs_seek(s, read)) {
MP_VERBOSE(s, "Dropping cache at pos %"PRId64", "
"cached range: %"PRId64"-%"PRId64".\n", read,
@@ -262,8 +260,10 @@ static bool cache_update_stream_position(struct priv *s)
if (stream_tell(s->stream) != s->max_filepos && s->seekable) {
MP_VERBOSE(s, "Seeking underlying stream: %"PRId64" -> %"PRId64"\n",
stream_tell(s->stream), s->max_filepos);
- if (!stream_seek(s->stream, s->max_filepos))
+ if (!stream_seek(s->stream, s->max_filepos)) {
+ s->read_seek_failed = true;
return false;
+ }
}
return stream_tell(s->stream) == s->max_filepos;
@@ -606,11 +606,25 @@ static int cache_fill_buffer(struct stream *cache, char *buffer, int max_len)
s->idle = false;
if (!cache_wakeup_and_wait(s, &retry_time))
break;
+ if (s->read_seek_failed) {
+ MP_ERR(s, "error reading after async seek failed\n");
+ s->read_seek_failed = false;
+ break;
+ }
}
}
- // wakeup the cache thread, possibly make it read more data ahead
- pthread_cond_signal(&s->wakeup);
+ if (!s->eof) {
+ // wakeup the cache thread, possibly make it read more data ahead
+ // this is throttled to reduce excessive wakeups during normal reading
+ // (using the amount of bytes after which the cache thread most likely
+ // can actually read new data)
+ s->bytes_until_wakeup -= readb;
+ if (s->bytes_until_wakeup <= 0) {
+ s->bytes_until_wakeup = MPMAX(FILL_LIMIT, s->stream->read_chunk);
+ pthread_cond_signal(&s->wakeup);
+ }
+ }
pthread_mutex_unlock(&s->mutex);
return readb;
}
@@ -656,6 +670,8 @@ static int cache_seek(stream_t *cache, int64_t pos)
}
}
+ s->bytes_until_wakeup = 0;
+
pthread_mutex_unlock(&s->mutex);
return r;
diff --git a/stream/dvb_tune.c b/stream/dvb_tune.c
index 09a2027..d9b350f 100644
--- a/stream/dvb_tune.c
+++ b/stream/dvb_tune.c
@@ -1,7 +1,7 @@
/* dvbtune - tune.c
Copyright (C) Dave Chapman 2001,2002
- Copyright (C) Rozhuk Ivan <rozhuk.im@gmail.com> 2016
+ Copyright (C) Rozhuk Ivan <rozhuk.im@gmail.com> 2016 - 2017
Modified for use with MPlayer, for details see the changelog at
http://svn.mplayerhq.hu/mplayer/trunk/
@@ -82,7 +82,7 @@ unsigned int dvb_get_tuner_delsys_mask(int fe_fd, struct mp_log *log)
#ifdef DVB_USE_S2API
/* S2API is the DVB API new since 2.6.28.
It allows to query frontends with multiple delivery systems. */
- mp_verbose(log, "Querying tuner type via DVBv5 API for frontend FD %d\n",
+ mp_verbose(log, "Querying tuner frontend type via DVBv5 API for frontend FD %d\n",
fe_fd);
prop[0].cmd = DTV_ENUM_DELSYS;
if (ioctl(fe_fd, FE_GET_PROPERTY, &cmdseq) < 0) {
@@ -98,14 +98,14 @@ unsigned int dvb_get_tuner_delsys_mask(int fe_fd, struct mp_log *log)
for (i = 0; i < delsys_count; i++) {
delsys = (unsigned int)prop[0].u.buffer.data[i];
DELSYS_SET(ret_mask, delsys);
- mp_verbose(log, "DVBv5: Tuner type seems to be %s\n", get_dvb_delsys(delsys));
+ mp_verbose(log, "DVBv5: Tuner frontend type seems to be %s\n", get_dvb_delsys(delsys));
}
return ret_mask;
old_api:
#endif
- mp_verbose(log, "Querying tuner type via pre-DVBv5 API for frontend FD %d\n",
+ mp_verbose(log, "Querying tuner frontend type via pre-DVBv5 API for frontend FD %d\n",
fe_fd);
memset(&fe_info, 0x00, sizeof(struct dvb_frontend_info));
@@ -119,7 +119,7 @@ old_api:
prop[0].u.data = 0x0300; /* Fail, assume 3.0 */
}
- mp_verbose(log, "DVBv3: Queried tuner type of device named '%s', FD: %d\n",
+ mp_verbose(log, "DVBv3: Queried tuner frontend type of device named '%s', FD: %d\n",
fe_info.name, fe_fd);
switch (fe_info.type) {
case FE_OFDM:
@@ -152,37 +152,36 @@ old_api:
if ((FE_CAN_8VSB | FE_CAN_16VSB) & fe_info.caps) {
DELSYS_SET(ret_mask, SYS_ATSC);
}
-#if 0 /* Not used now. */
if ((FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_QAM_AUTO) & fe_info.caps) {
DELSYS_SET(ret_mask, SYS_DVBC_ANNEX_B);
}
-#endif
break;
#endif
default:
- mp_err(log, "DVBv3: Unknown tuner type: %d\n", fe_info.type);
+ mp_err(log, "DVBv3: Unknown tuner frontend type: %d\n", fe_info.type);
return ret_mask;
}
for (delsys = 0; delsys < SYS_DVB__COUNT__; delsys ++) {
if (!DELSYS_IS_SET(ret_mask, delsys))
continue; /* Skip unsupported. */
- mp_verbose(log, "DVBv3: Tuner type seems to be %s\n", get_dvb_delsys(delsys));
+ mp_verbose(log, "DVBv3: Tuner frontend type seems to be %s\n", get_dvb_delsys(delsys));
}
return ret_mask;
}
-int dvb_open_devices(dvb_priv_t *priv, unsigned int n, unsigned int demux_cnt)
+int dvb_open_devices(dvb_priv_t *priv, unsigned int adapter,
+ unsigned int frontend, unsigned int demux_cnt)
{
unsigned int i;
char frontend_dev[PATH_MAX], dvr_dev[PATH_MAX], demux_dev[PATH_MAX];
-
dvb_state_t* state = priv->state;
- snprintf(frontend_dev, sizeof(frontend_dev), "/dev/dvb/adapter%u/frontend0", n);
- snprintf(dvr_dev, sizeof(dvr_dev), "/dev/dvb/adapter%u/dvr0", n);
- snprintf(demux_dev, sizeof(demux_dev), "/dev/dvb/adapter%u/demux0", n);
+ snprintf(frontend_dev, sizeof(frontend_dev), "/dev/dvb/adapter%u/frontend%u", adapter, frontend);
+ snprintf(dvr_dev, sizeof(dvr_dev), "/dev/dvb/adapter%u/dvr0", adapter);
+ snprintf(demux_dev, sizeof(demux_dev), "/dev/dvb/adapter%u/demux0", adapter);
+ MP_VERBOSE(priv, "DVB_OPEN_DEVICES: frontend: %s\n", frontend_dev);
state->fe_fd = open(frontend_dev, O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (state->fe_fd < 0) {
MP_ERR(priv, "ERROR OPENING FRONTEND DEVICE %s: ERRNO %d\n",
@@ -464,6 +463,26 @@ static int do_diseqc(int secfd, int sat_no, int polv, int hi_lo)
((sat_no / 4) % 2) ? SEC_MINI_B : SEC_MINI_A);
}
+#ifdef DVB_USE_S2API
+static int dvbv5_tune(dvb_priv_t *priv, int fd_frontend,
+ unsigned int delsys, struct dtv_properties* cmdseq)
+{
+ MP_VERBOSE(priv, "Tuning via S2API, channel is %s.\n",
+ get_dvb_delsys(delsys));
+ MP_VERBOSE(priv, "Dumping raw tuning commands and values:\n");
+ for (int i = 0; i < cmdseq->num; ++i) {
+ MP_VERBOSE(priv, "%02d: 0x%x(%d) => 0x%x(%d)\n",
+ i, cmdseq->props[i].cmd, cmdseq->props[i].cmd,
+ cmdseq->props[i].u.data, cmdseq->props[i].u.data);
+ }
+ if (ioctl(fd_frontend, FE_SET_PROPERTY, cmdseq) < 0) {
+ MP_ERR(priv, "ERROR tuning channel\n");
+ return -1;
+ }
+ return 0;
+}
+#endif
+
static int tune_it(dvb_priv_t *priv, int fd_frontend, unsigned int delsys,
unsigned int freq, unsigned int srate, char pol,
int stream_id,
@@ -578,6 +597,7 @@ static int tune_it(dvb_priv_t *priv, int fd_frontend, unsigned int delsys,
break;
#ifdef DVB_ATSC
case SYS_ATSC:
+ case SYS_DVBC_ANNEX_B:
MP_VERBOSE(priv, "tuning %s to %d, modulation=%d\n",
get_dvb_delsys(delsys), freq, modulation);
break;
@@ -606,31 +626,111 @@ static int tune_it(dvb_priv_t *priv, int fd_frontend, unsigned int delsys,
}
/* Tune. */
- struct dtv_property p[] = {
- { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys },
- { .cmd = DTV_FREQUENCY, .u.data = freq },
- { .cmd = DTV_MODULATION, .u.data = modulation },
- { .cmd = DTV_SYMBOL_RATE, .u.data = srate },
- { .cmd = DTV_INNER_FEC, .u.data = HP_CodeRate },
- { .cmd = DTV_INVERSION, .u.data = specInv },
- { .cmd = DTV_ROLLOFF, .u.data = ROLLOFF_AUTO },
- { .cmd = DTV_BANDWIDTH_HZ, .u.data = bandwidth_hz },
- { .cmd = DTV_PILOT, .u.data = PILOT_AUTO },
- { .cmd = DTV_STREAM_ID, .u.data = stream_id },
- { .cmd = DTV_TUNE },
- };
- struct dtv_properties cmdseq = {
- .num = sizeof(p) / sizeof(p[0]),
- .props = p
- };
- MP_VERBOSE(priv, "Tuning via S2API, channel is %s.\n",
- get_dvb_delsys(delsys));
- if (ioctl(fd_frontend, FE_SET_PROPERTY, &cmdseq) < 0) {
- MP_ERR(priv, "ERROR tuning channel\n");
- goto old_api;
+ switch (delsys) {
+ case SYS_DVBS:
+ case SYS_DVBS2:
+ {
+ struct dtv_property p[] = {
+ { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys },
+ { .cmd = DTV_FREQUENCY, .u.data = freq },
+ { .cmd = DTV_MODULATION, .u.data = modulation },
+ { .cmd = DTV_SYMBOL_RATE, .u.data = srate },
+ { .cmd = DTV_INNER_FEC, .u.data = HP_CodeRate },
+ { .cmd = DTV_INVERSION, .u.data = specInv },
+ { .cmd = DTV_ROLLOFF, .u.data = ROLLOFF_AUTO },
+ { .cmd = DTV_PILOT, .u.data = PILOT_AUTO },
+ { .cmd = DTV_TUNE },
+ };
+ struct dtv_properties cmdseq = {
+ .num = sizeof(p) / sizeof(p[0]),
+ .props = p
+ };
+ if (dvbv5_tune(priv, fd_frontend, delsys, &cmdseq) != 0) {
+ goto old_api;
+ }
+ }
+ break;
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ {
+ struct dtv_property p[] = {
+ { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys },
+ { .cmd = DTV_FREQUENCY, .u.data = freq },
+ { .cmd = DTV_MODULATION, .u.data = modulation },
+ { .cmd = DTV_SYMBOL_RATE, .u.data = srate },
+ { .cmd = DTV_CODE_RATE_HP, .u.data = HP_CodeRate },
+ { .cmd = DTV_CODE_RATE_LP, .u.data = LP_CodeRate },
+ { .cmd = DTV_INVERSION, .u.data = specInv },
+ { .cmd = DTV_BANDWIDTH_HZ, .u.data = bandwidth_hz },
+ { .cmd = DTV_TRANSMISSION_MODE, .u.data = TransmissionMode },
+ { .cmd = DTV_GUARD_INTERVAL, .u.data = guardInterval },
+ { .cmd = DTV_HIERARCHY, .u.data = hier },
+ { .cmd = DTV_STREAM_ID, .u.data = stream_id },
+ { .cmd = DTV_TUNE },
+ };
+ struct dtv_properties cmdseq = {
+ .num = sizeof(p) / sizeof(p[0]),
+ .props = p
+ };
+ if (dvbv5_tune(priv, fd_frontend, delsys, &cmdseq) != 0) {
+ goto old_api;
+ }
+ }
+ break;
+ case SYS_DVBC_ANNEX_A:
+ case SYS_DVBC_ANNEX_C:
+ {
+ struct dtv_property p[] = {
+ { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys },
+ { .cmd = DTV_FREQUENCY, .u.data = freq },
+ { .cmd = DTV_MODULATION, .u.data = modulation },
+ { .cmd = DTV_SYMBOL_RATE, .u.data = srate },
+ { .cmd = DTV_INNER_FEC, .u.data = HP_CodeRate },
+ { .cmd = DTV_INVERSION, .u.data = specInv },
+ { .cmd = DTV_TUNE },
+ };
+ struct dtv_properties cmdseq = {
+ .num = sizeof(p) / sizeof(p[0]),
+ .props = p
+ };
+ if (dvbv5_tune(priv, fd_frontend, delsys, &cmdseq) != 0) {
+ goto old_api;
+ }
+ }
+ break;
+#ifdef DVB_ATSC
+ case SYS_ATSC:
+ case SYS_DVBC_ANNEX_B:
+ {
+ struct dtv_property p[] = {
+ { .cmd = DTV_DELIVERY_SYSTEM, .u.data = delsys },
+ { .cmd = DTV_FREQUENCY, .u.data = freq },
+ { .cmd = DTV_INVERSION, .u.data = specInv },
+ { .cmd = DTV_MODULATION, .u.data = modulation },
+ { .cmd = DTV_TUNE },
+ };
+ struct dtv_properties cmdseq = {
+ .num = sizeof(p) / sizeof(p[0]),
+ .props = p
+ };
+ if (dvbv5_tune(priv, fd_frontend, delsys, &cmdseq) != 0) {
+ goto old_api;
+ }
+ }
+ break;
+#endif
}
- return check_status(priv, fd_frontend, timeout);
+ int tune_status = check_status(priv, fd_frontend, timeout);
+ if (tune_status != 0) {
+ MP_ERR(priv, "ERROR locking to channel when tuning with S2API, clearing and falling back to DVBv3-tuning.\n");
+ if (ioctl(fd_frontend, FE_SET_PROPERTY, &cmdseq_clear) < 0) {
+ MP_ERR(priv, "FE_SET_PROPERTY DTV_CLEAR failed\n");
+ }
+ goto old_api;
+ } else {
+ return tune_status;
+ }
old_api:
#endif
@@ -669,6 +769,7 @@ old_api:
break;
#ifdef DVB_ATSC
case SYS_ATSC:
+ case SYS_DVBC_ANNEX_B:
feparams.u.vsb.modulation = modulation;
break;
#endif
diff --git a/stream/dvb_tune.h b/stream/dvb_tune.h
index 756f730..d7a7901 100644
--- a/stream/dvb_tune.h
+++ b/stream/dvb_tune.h
@@ -26,7 +26,8 @@ struct mp_log;
const char *get_dvb_delsys(unsigned int delsys);
unsigned int dvb_get_tuner_delsys_mask(int fe_fd, struct mp_log *log);
-int dvb_open_devices(dvb_priv_t *priv, unsigned int n, unsigned int demux_cnt);
+int dvb_open_devices(dvb_priv_t *priv, unsigned int adapter,
+ unsigned int frontend, unsigned int demux_cnt);
int dvb_fix_demuxes(dvb_priv_t *priv, unsigned int cnt);
int dvb_set_ts_filt(dvb_priv_t *priv, int fd, uint16_t pid, dmx_pes_type_t pestype);
int dvb_get_pmt_pid(dvb_priv_t *priv, int card, int service_id);
diff --git a/stream/dvbin.h b/stream/dvbin.h
index 02b6a72..01a7058 100644
--- a/stream/dvbin.h
+++ b/stream/dvbin.h
@@ -11,6 +11,10 @@
#include "config.h"
#include "stream.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
#define SLOF (11700 * 1000UL)
#define LOF1 (9750 * 1000UL)
#define LOF2 (10600 * 1000UL)
@@ -22,6 +26,9 @@
#include <linux/dvb/audio.h>
#include <linux/dvb/version.h>
+#define MAX_ADAPTERS 16
+#define MAX_FRONTENDS 8
+
#undef DVB_ATSC
#if defined(DVB_API_VERSION_MINOR)
@@ -65,7 +72,8 @@ typedef struct {
unsigned int freq, srate, diseqc;
char pol;
unsigned int tpid, dpid1, dpid2, progid, ca, pids[DMX_FILTER_SIZE], pids_cnt;
- bool is_dvb_x2;
+ bool is_dvb_x2; /* Used only in dvb_get_channels() and parse_vdr_par_string(), use delsys. */
+ unsigned int frontend;
unsigned int delsys;
unsigned int stream_id;
unsigned int service_id;
@@ -86,7 +94,7 @@ typedef struct {
typedef struct {
int devno;
- unsigned int delsys_mask;
+ unsigned int delsys_mask[MAX_FRONTENDS];
dvb_channels_list_t *list;
} dvb_adapter_config_t;
@@ -94,6 +102,7 @@ typedef struct {
unsigned int adapters_count;
dvb_adapter_config_t *adapters;
unsigned int cur_adapter;
+ unsigned int cur_frontend;
int fe_fd;
int dvr_fd;
@@ -124,6 +133,7 @@ typedef struct {
/* Keep in sync with enum fe_delivery_system. */
#ifndef DVB_USE_S2API
# define SYS_DVBC_ANNEX_A 1
+# define SYS_DVBC_ANNEX_B 1
# define SYS_DVBT 3
# define SYS_DVBS 5
# define SYS_DVBS2 6
@@ -151,6 +161,7 @@ typedef struct {
DELSYS_BIT(SYS_DVBS) | \
DELSYS_BIT(SYS_DVBS2) | \
DELSYS_BIT(SYS_ATSC) | \
+ DELSYS_BIT(SYS_DVBC_ANNEX_B) | \
DELSYS_BIT(SYS_DVBT2) | \
DELSYS_BIT(SYS_DVBC_ANNEX_C) \
)
diff --git a/stream/frequencies.h b/stream/frequencies.h
index be4a33a..e215d02 100644
--- a/stream/frequencies.h
+++ b/stream/frequencies.h
@@ -125,4 +125,9 @@ struct CHANLISTS {
extern const struct CHANLISTS chanlists[];
extern const int chancount;
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
#endif /* MPLAYER_FREQUENCIES_H */
diff --git a/stream/stream.c b/stream/stream.c
index 29234e5..44a773c 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -251,7 +251,7 @@ static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
MP_VERBOSE(s, "Opening %s\n", url);
if ((s->mode & STREAM_WRITE) && !sinfo->can_write) {
- MP_VERBOSE(s, "No write access implemented.\n");
+ MP_DBG(s, "No write access implemented.\n");
talloc_free(s);
return STREAM_NO_MATCH;
}
@@ -273,7 +273,7 @@ static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
if (s->mime_type)
MP_VERBOSE(s, "Mime-type: '%s'\n", s->mime_type);
- MP_VERBOSE(s, "Stream opened successfully.\n");
+ MP_DBG(s, "Stream opened successfully.\n");
*ret = s;
return STREAM_OK;
diff --git a/stream/stream_cdda.c b/stream/stream_cdda.c
index 2a8eb75..7fd461a 100644
--- a/stream/stream_cdda.c
+++ b/stream/stream_cdda.c
@@ -46,6 +46,11 @@
#include "common/msg.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
typedef struct cdda_params {
cdrom_drive_t *cd;
cdrom_paranoia_t *cdp;
diff --git a/stream/stream_dvb.c b/stream/stream_dvb.c
index 0638b17..02c5878 100644
--- a/stream/stream_dvb.c
+++ b/stream/stream_dvb.c
@@ -2,7 +2,7 @@
dvbstream
(C) Dave Chapman <dave@dchapman.com> 2001, 2002.
- (C) Rozhuk Ivan <rozhuk.im@gmail.com> 2016
+ (C) Rozhuk Ivan <rozhuk.im@gmail.com> 2016 - 2017
Original authors: Nico, probably Arpi
@@ -58,7 +58,10 @@
#include "dvbin.h"
#include "dvb_tune.h"
-#define MAX_ADAPTERS 16
+#if !HAVE_GPL
+#error GPL only
+#endif
+
#define CHANNEL_LINE_LEN 256
#define min(a, b) ((a) <= (b) ? (a) : (b))
@@ -86,6 +89,50 @@ const struct m_sub_options stream_dvb_conf = {
void dvbin_close(stream_t *stream);
+static fe_modulation_t parse_vdr_modulation(const char** modstring) {
+ if (!strncmp(*modstring, "16", 2)) {
+ (*modstring)+=2;
+ return QAM_16;
+ } else if (!strncmp(*modstring, "32", 2)) {
+ (*modstring)+=2;
+ return QAM_32;
+ } else if (!strncmp(*modstring, "64", 2)) {
+ (*modstring)+=2;
+ return QAM_64;
+ } else if (!strncmp(*modstring, "128", 3)) {
+ (*modstring)+=3;
+ return QAM_128;
+ } else if (!strncmp(*modstring, "256", 3)) {
+ (*modstring)+=3;
+ return QAM_256;
+ } else if (!strncmp(*modstring, "998", 3)) {
+ (*modstring)+=3;
+ return QAM_AUTO;
+ } else if (!strncmp(*modstring, "2", 1)) {
+ (*modstring)++;
+ return QPSK;
+ } else if (!strncmp(*modstring, "5", 1)) {
+ (*modstring)++;
+ return PSK_8;
+ } else if (!strncmp(*modstring, "6", 1)) {
+ (*modstring)++;
+ return APSK_16;
+ } else if (!strncmp(*modstring, "7", 1)) {
+ (*modstring)++;
+ return APSK_32;
+ } else if (!strncmp(*modstring, "10", 2)) {
+ (*modstring)+=2;
+ return VSB_8;
+ } else if (!strncmp(*modstring, "11", 2)) {
+ (*modstring)+=2;
+ return VSB_16;
+ } else if (!strncmp(*modstring, "12", 2)) {
+ (*modstring)+=2;
+ return DQPSK;
+ } else {
+ return QAM_AUTO;
+ }
+}
static void parse_vdr_par_string(const char *vdr_par_str, dvb_channel_t *ptr)
{
@@ -131,6 +178,10 @@ static void parse_vdr_par_string(const char *vdr_par_str, dvb_channel_t *ptr)
}
vdr_par++;
break;
+ case 'M':
+ vdr_par++;
+ ptr->mod = parse_vdr_modulation(&vdr_par);
+ break;
default:
vdr_par++;
}
@@ -201,6 +252,7 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
dvb_channels_list_t *list_add,
int cfg_full_transponder,
char *filename,
+ unsigned int frontend,
int delsys, unsigned int delsys_mask)
{
dvb_channels_list_t *list = list_add;
@@ -279,6 +331,7 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
ptr->freq = 0;
ptr->service_id = -1;
ptr->is_dvb_x2 = false;
+ ptr->frontend = frontend;
ptr->delsys = delsys;
ptr->diseqc = 0;
ptr->stream_id = NO_STREAM_ID_FILTER;
@@ -299,9 +352,13 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
&num_chars);
if (num_chars == strlen(&line[k])) {
+ // Modulation parsed here, not via old xine-parsing path.
+ mod[0] = '\0';
// It's a VDR-style config line.
parse_vdr_par_string(vdr_par_str, ptr);
- // We still need the special SAT-handling here.
+ // Units in VDR-style config files are divided by 1000.
+ ptr->freq *= 1000UL;
+ ptr->srate *= 1000UL;
switch (delsys) {
case SYS_DVBT:
case SYS_DVBT2:
@@ -317,6 +374,7 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
case SYS_DVBC_ANNEX_A:
case SYS_DVBC_ANNEX_C:
case SYS_ATSC:
+ case SYS_DVBC_ANNEX_B:
mp_verbose(log, "VDR, %s, NUM: %d, NUM_FIELDS: %d, NAME: %s, "
"FREQ: %d, SRATE: %d",
get_dvb_delsys(delsys),
@@ -334,9 +392,6 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
if (!DELSYS_IS_SET(delsys_mask, delsys))
continue; /* Skip channel. */
- ptr->freq *= 1000UL;
- ptr->srate *= 1000UL;
-
if (vdr_loc_str[0]) {
// In older vdr config format, this field contained the DISEQc information.
// If it is numeric, assume that's it.
@@ -391,6 +446,7 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
break;
#ifdef DVB_ATSC
case SYS_ATSC:
+ case SYS_DVBC_ANNEX_B:
fields = sscanf(&line[k], atsc_conf,
&ptr->freq, mod, vpid_str, apid_str);
mp_verbose(log, "%s, NUM: %d, NUM_FIELDS: %d, NAME: %s, FREQ: %d\n",
@@ -524,6 +580,7 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
case SYS_DVBC_ANNEX_A:
case SYS_DVBC_ANNEX_C:
case SYS_ATSC:
+ case SYS_DVBC_ANNEX_B:
if (!strcmp(mod, "QAM_128")) {
ptr->mod = QAM_128;
} else if (!strcmp(mod, "QAM_256")) {
@@ -542,6 +599,21 @@ static dvb_channels_list_t *dvb_get_channels(struct mp_log *log,
#endif
}
}
+#ifdef DVB_ATSC
+ /* Modulation defines real delsys for ATSC:
+ Terrestrial (VSB) is SYS_ATSC, Cable (QAM) is SYS_DVBC_ANNEX_B. */
+ if (delsys == SYS_ATSC || delsys == SYS_DVBC_ANNEX_B) {
+ if (ptr->mod == VSB_8 || ptr->mod == VSB_16) {
+ delsys = SYS_ATSC;
+ } else {
+ delsys = SYS_DVBC_ANNEX_B;
+ }
+ if (!DELSYS_IS_SET(delsys_mask, delsys))
+ continue; /* Skip channel. */
+ mp_verbose(log, "Switched to delivery system for ATSC: %s (guessed from modulation).\n",
+ get_dvb_delsys(delsys));
+ }
+#endif
switch (delsys) {
case SYS_DVBT:
@@ -720,9 +792,10 @@ int dvb_set_channel(stream_t *stream, unsigned int adapter, unsigned int n)
state->retry = 0;
//empty both the stream's and driver's buffer
while (dvb_streaming_read(stream, buf, sizeof(buf)) > 0) {}
- if (state->cur_adapter != adapter) {
+ if (state->cur_adapter != adapter ||
+ state->cur_frontend != channel->frontend) {
dvbin_close(stream);
- if (!dvb_open_devices(priv, devno, channel->pids_cnt)) {
+ if (!dvb_open_devices(priv, devno, channel->frontend, channel->pids_cnt)) {
MP_ERR(stream, "DVB_SET_CHANNEL, COULDN'T OPEN DEVICES OF "
"ADAPTER: %d, EXIT\n", devno);
return 0;
@@ -734,14 +807,13 @@ int dvb_set_channel(stream_t *stream, unsigned int adapter, unsigned int n)
return 0;
}
} else {
- if (!dvb_open_devices(priv, devno, channel->pids_cnt)) {
+ if (!dvb_open_devices(priv, devno, channel->frontend, channel->pids_cnt)) {
MP_ERR(stream, "DVB_SET_CHANNEL2, COULDN'T OPEN DEVICES OF "
"ADAPTER: %d, EXIT\n", devno);
return 0;
}
}
- state->cur_adapter = adapter;
state->retry = 5;
new_list->current = n;
MP_VERBOSE(stream, "DVB_SET_CHANNEL: new channel name=%s, adapter: %d, "
@@ -759,8 +831,10 @@ int dvb_set_channel(stream_t *stream, unsigned int adapter, unsigned int n)
return 0;
}
- state->last_freq = channel->freq;
state->is_on = 1;
+ state->last_freq = channel->freq;
+ state->cur_adapter = adapter;
+ state->cur_frontend = channel->frontend;
if (channel->service_id != -1) {
/* We need the PMT-PID in addition.
@@ -923,6 +997,8 @@ void dvbin_close(stream_t *stream)
state->fe_fd = state->dvr_fd = -1;
state->is_on = 0;
+ state->cur_adapter = -1;
+ state->cur_frontend = -1;
pthread_mutex_lock(&global_dvb_state_lock);
dvb_free_state(state);
@@ -973,8 +1049,6 @@ static int dvb_streaming_start(stream_t *stream, char *progname)
}
-
-
static int dvb_open(stream_t *stream)
{
// I don't force the file format because, although it's almost always TS,
@@ -1010,6 +1084,7 @@ static int dvb_open(stream_t *stream)
// The following setup only has to be done once.
state->cur_adapter = -1;
+ state->cur_frontend = -1;
for (i = 0; i < state->adapters_count; i++) {
if (state->adapters[i].devno == priv->cfg_devno) {
state->cur_adapter = i;
@@ -1062,7 +1137,7 @@ dvb_state_t *dvb_get_state(stream_t *stream)
struct mp_log *log = stream->log;
struct mpv_global *global = stream->global;
dvb_priv_t *priv = stream->priv;
- unsigned int delsys, delsys_mask, size;
+ unsigned int delsys, delsys_mask[MAX_FRONTENDS], size;
char filename[PATH_MAX], *conf_file;
const char *conf_file_name;
void *talloc_ctx;
@@ -1098,75 +1173,74 @@ dvb_state_t *dvb_get_state(stream_t *stream)
state->switching_channel = false;
state->stream_used = true;
state->fe_fd = state->dvr_fd = -1;
- for (int i = 0; i < MAX_ADAPTERS; i++) {
- snprintf(filename, sizeof(filename), "/dev/dvb/adapter%d/frontend0", i);
- int fd = open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
- if (fd < 0) {
- mp_verbose(log, "DVB_CONFIG, can't open device %s, skipping\n",
- filename);
- continue;
- }
-
- mp_verbose(log, "Opened device %s, FD: %d\n", filename, fd);
- delsys_mask = dvb_get_tuner_delsys_mask(fd, log);
- delsys_mask &= DELSYS_SUPP_MASK; /* Filter unsupported delivery systems. */
- close(fd);
- if (delsys_mask == 0) {
- mp_verbose(log, "Frontend device %s has no supported delivery systems.\n",
- filename);
- continue; /* Skip tuner. */
- }
- mp_verbose(log, "Frontend device %s offers some supported delivery systems.\n",
- filename);
- /* Create channel list for adapter. */
+ for (unsigned int i = 0; i < MAX_ADAPTERS; i++) {
list = NULL;
- for (delsys = 0; delsys < SYS_DVB__COUNT__; delsys++) {
- if (!DELSYS_IS_SET(delsys_mask, delsys))
- continue; /* Skip unsupported. */
-
- switch (delsys) {
- case SYS_DVBC_ANNEX_A:
- case SYS_DVBC_ANNEX_C:
- conf_file_name = "channels.conf.cbl";
- break;
- case SYS_ATSC:
- conf_file_name = "channels.conf.atsc";
- break;
- case SYS_DVBT:
- if (DELSYS_IS_SET(delsys_mask, SYS_DVBT2))
- continue; /* Add all channels later with T2. */
- /* PASSTOUTH */
- case SYS_DVBT2:
- conf_file_name = "channels.conf.ter";
- break;
- case SYS_DVBS:
- if (DELSYS_IS_SET(delsys_mask, SYS_DVBS2))
- continue; /* Add all channels later with S2. */
- /* PASSTOUTH */
- case SYS_DVBS2:
- conf_file_name = "channels.conf.sat";
- break;
- default:
+ for (unsigned int f = 0; f < MAX_FRONTENDS; f++) {
+ snprintf(filename, sizeof(filename), "/dev/dvb/adapter%u/frontend%u", i, f);
+ int fd = open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (fd < 0)
continue;
+
+ mp_verbose(log, "Opened device %s, FD: %d\n", filename, fd);
+ delsys_mask[f] = dvb_get_tuner_delsys_mask(fd, log);
+ delsys_mask[f] &= DELSYS_SUPP_MASK; /* Filter unsupported delivery systems. */
+ close(fd);
+ if (delsys_mask[f] == 0) {
+ mp_verbose(log, "Frontend device %s has no supported delivery systems.\n",
+ filename);
+ continue; /* Skip tuner. */
}
+ mp_verbose(log, "Frontend device %s offers some supported delivery systems.\n",
+ filename);
+ /* Create channel list for adapter. */
+ for (delsys = 0; delsys < SYS_DVB__COUNT__; delsys++) {
+ if (!DELSYS_IS_SET(delsys_mask[f], delsys))
+ continue; /* Skip unsupported. */
+
+ switch (delsys) {
+ case SYS_DVBC_ANNEX_A:
+ case SYS_DVBC_ANNEX_C:
+ conf_file_name = "channels.conf.cbl";
+ break;
+ case SYS_ATSC:
+ conf_file_name = "channels.conf.atsc";
+ break;
+ case SYS_DVBT:
+ if (DELSYS_IS_SET(delsys_mask[f], SYS_DVBT2))
+ continue; /* Add all channels later with T2. */
+ /* PASSTOUTH */
+ case SYS_DVBT2:
+ conf_file_name = "channels.conf.ter";
+ break;
+ case SYS_DVBS:
+ if (DELSYS_IS_SET(delsys_mask[f], SYS_DVBS2))
+ continue; /* Add all channels later with S2. */
+ /* PASSTOUTH */
+ case SYS_DVBS2:
+ conf_file_name = "channels.conf.sat";
+ break;
+ default:
+ continue;
+ }
- if (priv->cfg_file && priv->cfg_file[0]) {
- talloc_ctx = NULL;
- conf_file = priv->cfg_file;
- } else {
- talloc_ctx = talloc_new(NULL);
- conf_file = mp_find_config_file(talloc_ctx, global, conf_file_name);
- if (conf_file) {
- mp_verbose(log, "Ignoring other channels.conf files.\n");
+ if (priv->cfg_file && priv->cfg_file[0]) {
+ talloc_ctx = NULL;
+ conf_file = priv->cfg_file;
} else {
- conf_file = mp_find_config_file(talloc_ctx, global,
- "channels.conf");
+ talloc_ctx = talloc_new(NULL);
+ conf_file = mp_find_config_file(talloc_ctx, global, conf_file_name);
+ if (conf_file) {
+ mp_verbose(log, "Ignoring other channels.conf files.\n");
+ } else {
+ conf_file = mp_find_config_file(talloc_ctx, global,
+ "channels.conf");
+ }
}
- }
- list = dvb_get_channels(log, list, priv->cfg_full_transponder, conf_file,
- delsys, delsys_mask);
- talloc_free(talloc_ctx);
+ list = dvb_get_channels(log, list, priv->cfg_full_transponder,
+ conf_file, f, delsys, delsys_mask[f]);
+ talloc_free(talloc_ctx);
+ }
}
/* Add adapter with non zero channel list. */
if (list == NULL)
@@ -1185,7 +1259,8 @@ dvb_state_t *dvb_get_state(stream_t *stream)
state->adapters = adapters;
state->adapters[state->adapters_count].devno = i;
- state->adapters[state->adapters_count].delsys_mask = delsys_mask;
+ memcpy(&state->adapters[state->adapters_count].delsys_mask,
+ &delsys_mask, (sizeof(unsigned int) * MAX_FRONTENDS));
state->adapters[state->adapters_count].list = list;
state->adapters_count++;
}
diff --git a/stream/stream_dvd_common.h b/stream/stream_dvd_common.h
index 8caa281..dccf492 100644
--- a/stream/stream_dvd_common.h
+++ b/stream/stream_dvd_common.h
@@ -22,6 +22,11 @@
#include <stdbool.h>
#include "stream.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
extern const char * const dvd_audio_stream_channels[6];
extern const char * const dvd_audio_stream_types[8];
diff --git a/stream/stream_dvdnav.c b/stream/stream_dvdnav.c
index fc7ddfd..16a3d11 100644
--- a/stream/stream_dvdnav.c
+++ b/stream/stream_dvdnav.c
@@ -19,6 +19,10 @@
#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
diff --git a/stream/stream_libarchive.c b/stream/stream_libarchive.c
index a840de7..2dddb45 100644
--- a/stream/stream_libarchive.c
+++ b/stream/stream_libarchive.c
@@ -126,12 +126,32 @@ static int switch_cb(struct archive *arch, void *oldpriv, void *newpriv)
return open_cb(arch, newpriv);
}
-void mp_archive_free(struct mp_archive *mpa)
+static void mp_archive_close(struct mp_archive *mpa)
{
if (mpa && mpa->arch) {
archive_read_close(mpa->arch);
archive_read_free(mpa->arch);
+ mpa->arch = NULL;
}
+}
+
+// Supposedly we're not allowed to continue reading on FATAL returns. Otherwise
+// crashes and other UB is possible. Assume calling the close/free functions is
+// still ok. Return true if it was fatal and the archive was closed.
+static bool mp_archive_check_fatal(struct mp_archive *mpa, int r)
+{
+ if (r > ARCHIVE_FATAL)
+ return false;
+ MP_FATAL(mpa, "fatal error received - closing archive\n");
+ mp_archive_close(mpa);
+ return true;
+}
+
+void mp_archive_free(struct mp_archive *mpa)
+{
+ mp_archive_close(mpa);
+ if (mpa && mpa->locale)
+ freelocale(mpa->locale);
talloc_free(mpa);
}
@@ -211,7 +231,10 @@ static bool add_volume(struct mp_log *log, struct mp_archive *mpa,
vol->mpa = mpa;
vol->src = src;
vol->url = talloc_strdup(vol, url);
- return archive_read_append_callback_data(mpa->arch, vol) == ARCHIVE_OK;
+ locale_t oldlocale = uselocale(mpa->locale);
+ bool res = archive_read_append_callback_data(mpa->arch, vol) == ARCHIVE_OK;
+ uselocale(oldlocale);
+ return res;
}
struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
@@ -219,6 +242,9 @@ struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
{
struct mp_archive *mpa = talloc_zero(NULL, struct mp_archive);
mpa->log = log;
+ mpa->locale = newlocale(LC_ALL_MASK, "C.UTF-8", (locale_t)0);
+ if (!mpa->locale)
+ goto err;
mpa->arch = archive_read_new();
mpa->primary_src = src;
if (!mpa->arch)
@@ -238,6 +264,8 @@ struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
}
talloc_free(volumes);
+ locale_t oldlocale = uselocale(mpa->locale);
+
archive_read_support_format_7zip(mpa->arch);
archive_read_support_format_iso9660(mpa->arch);
archive_read_support_format_rar(mpa->arch);
@@ -257,7 +285,11 @@ struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
archive_read_set_close_callback(mpa->arch, close_cb);
if (mpa->primary_src->seekable)
archive_read_set_seek_callback(mpa->arch, seek_cb);
- if (archive_read_open1(mpa->arch) < ARCHIVE_OK)
+ bool fail = archive_read_open1(mpa->arch) < ARCHIVE_OK;
+
+ uselocale(oldlocale);
+
+ if (fail)
goto err;
return mpa;
@@ -274,6 +306,12 @@ bool mp_archive_next_entry(struct mp_archive *mpa)
talloc_free(mpa->entry_filename);
mpa->entry_filename = NULL;
+ if (!mpa->arch)
+ return false;
+
+ locale_t oldlocale = uselocale(mpa->locale);
+ bool success = false;
+
while (!mp_cancel_test(mpa->primary_src->cancel)) {
struct archive_entry *entry;
int r = archive_read_next_header(mpa->arch, &entry);
@@ -283,6 +321,7 @@ bool mp_archive_next_entry(struct mp_archive *mpa)
MP_ERR(mpa, "%s\n", archive_error_string(mpa->arch));
if (r < ARCHIVE_WARN) {
MP_FATAL(mpa, "could not read archive entry\n");
+ mp_archive_check_fatal(mpa, r);
break;
}
if (archive_entry_filetype(entry) != AE_IFREG)
@@ -297,14 +336,18 @@ bool mp_archive_next_entry(struct mp_archive *mpa)
mpa->entry = entry;
mpa->entry_filename = talloc_strdup(mpa, fn);
mpa->entry_num += 1;
- return true;
+ success = true;
+ break;
}
- return false;
+ uselocale(oldlocale);
+
+ return success;
}
struct priv {
struct mp_archive *mpa;
+ bool broken_seek;
struct stream *src;
int64_t entry_size;
char *entry_name;
@@ -322,9 +365,11 @@ static int reopen_archive(stream_t *s)
struct mp_archive *mpa = p->mpa;
while (mp_archive_next_entry(mpa)) {
if (strcmp(p->entry_name, mpa->entry_filename) == 0) {
+ locale_t oldlocale = uselocale(mpa->locale);
p->entry_size = -1;
if (archive_entry_size_is_set(mpa->entry))
p->entry_size = archive_entry_size(mpa->entry);
+ uselocale(oldlocale);
return STREAM_OK;
}
}
@@ -340,19 +385,33 @@ static int archive_entry_fill_buffer(stream_t *s, char *buffer, int max_len)
struct priv *p = s->priv;
if (!p->mpa)
return 0;
+ locale_t oldlocale = uselocale(p->mpa->locale);
int r = archive_read_data(p->mpa->arch, buffer, max_len);
- if (r < 0)
+ if (r < 0) {
MP_ERR(s, "%s\n", archive_error_string(p->mpa->arch));
+ if (mp_archive_check_fatal(p->mpa, r)) {
+ mp_archive_free(p->mpa);
+ p->mpa = NULL;
+ }
+ }
+ uselocale(oldlocale);
return r;
}
static int archive_entry_seek(stream_t *s, int64_t newpos)
{
struct priv *p = s->priv;
- if (!p->mpa)
- return -1;
- if (archive_seek_data(p->mpa->arch, newpos, SEEK_SET) >= 0)
- return 1;
+ if (p->mpa && !p->broken_seek) {
+ locale_t oldlocale = uselocale(p->mpa->locale);
+ int r = archive_seek_data(p->mpa->arch, newpos, SEEK_SET);
+ uselocale(oldlocale);
+ if (r >= 0)
+ return 1;
+ MP_WARN(s, "possibly unsupported seeking - switching to reopening\n");
+ p->broken_seek = true;
+ if (reopen_archive(s) < STREAM_OK)
+ return -1;
+ }
// libarchive can't seek in most formats.
if (newpos < s->pos) {
// Hack seeking backwards into working by reopening the archive and
@@ -371,11 +430,18 @@ static int archive_entry_seek(stream_t *s, int64_t newpos)
return -1;
int size = MPMIN(newpos - s->pos, sizeof(buffer));
+ locale_t oldlocale = uselocale(p->mpa->locale);
int r = archive_read_data(p->mpa->arch, buffer, size);
if (r < 0) {
MP_ERR(s, "%s\n", archive_error_string(p->mpa->arch));
+ uselocale(oldlocale);
+ if (mp_archive_check_fatal(p->mpa, r)) {
+ mp_archive_free(p->mpa);
+ p->mpa = NULL;
+ }
return -1;
}
+ uselocale(oldlocale);
s->pos += r;
}
}
diff --git a/stream/stream_libarchive.h b/stream/stream_libarchive.h
index c15dc1b..8884834 100644
--- a/stream/stream_libarchive.h
+++ b/stream/stream_libarchive.h
@@ -1,6 +1,15 @@
+#include <locale.h>
+#include "osdep/io.h"
+
+#ifdef __APPLE__
+# include <string.h>
+# include <xlocale.h>
+#endif
+
struct mp_log;
struct mp_archive {
+ locale_t locale;
struct mp_log *log;
struct archive *arch;
struct stream *primary_src;
diff --git a/stream/stream_smb.c b/stream/stream_smb.c
index 79243a4..4376f71 100644
--- a/stream/stream_smb.c
+++ b/stream/stream_smb.c
@@ -26,6 +26,11 @@
#include "stream.h"
#include "options/m_option.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
struct priv {
int fd;
};
diff --git a/stream/tv.h b/stream/tv.h
index 64be222..f37e715 100644
--- a/stream/tv.h
+++ b/stream/tv.h
@@ -26,6 +26,11 @@
#include "osdep/endian.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
struct mp_log;
typedef struct tv_params {
diff --git a/sub/ass_mp.c b/sub/ass_mp.c
index 7006d53..03cc557 100644
--- a/sub/ass_mp.c
+++ b/sub/ass_mp.c
@@ -109,8 +109,8 @@ static const int map_ass_level[] = {
MSGL_INFO,
MSGL_V,
MSGL_V,
- MSGL_V, // 5 application recommended level
- MSGL_DEBUG,
+ MSGL_DEBUG, // 5 application recommended level
+ MSGL_TRACE,
MSGL_TRACE, // 7 "verbose DEBUG"
};
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index 743a06e..11ab879 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -203,7 +203,7 @@ void sub_preload(struct dec_sub *sub)
static bool is_new_segment(struct dec_sub *sub, struct demux_packet *p)
{
- return p->new_segment &&
+ return p->segmented &&
(p->start != sub->start || p->end != sub->end || p->codec != sub->codec);
}
diff --git a/sub/lavc_conv.c b/sub/lavc_conv.c
index 0f3b18e..06b9756 100644
--- a/sub/lavc_conv.c
+++ b/sub/lavc_conv.c
@@ -57,12 +57,13 @@ static const char *get_lavc_format(const char *format)
// We always want the user defined style instead.
static void disable_styles(bstr header)
{
+ bstr style = bstr0("\nStyle: ");
while (header.len) {
- int n = bstr_find(header, bstr0("\nStyle: "));
+ int n = bstr_find(header, style);
if (n < 0)
break;
header.start[n + 1] = '#'; // turn into a comment
- header = bstr_cut(header, 2);
+ header = bstr_cut(header, n + style.len);
}
}
@@ -237,6 +238,8 @@ char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet)
avsubtitle_free(&priv->cur);
mp_set_av_packet(&pkt, packet, &avctx->time_base);
+ if (pkt.pts < 0)
+ pkt.pts = 0;
if (strcmp(priv->codec, "webvtt-webm") == 0) {
if (parse_webvtt(&pkt, &parsed_pkt) < 0) {
diff --git a/sub/osd_libass.c b/sub/osd_libass.c
index 046007d..28a16d6 100644
--- a/sub/osd_libass.c
+++ b/sub/osd_libass.c
@@ -190,6 +190,7 @@ void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function)
static void mangle_ass(bstr *dst, const char *in)
{
+ const char *start = in;
bool escape_ass = true;
while (*in) {
// As used by osd_get_function_sym().
@@ -207,6 +208,12 @@ static void mangle_ass(bstr *dst, const char *in)
}
if (escape_ass && *in == '{')
bstr_xappend(NULL, dst, bstr0("\\"));
+ // Libass will strip leading whitespace
+ if (in[0] == ' ' && (in == start || in[-1] == '\n')) {
+ bstr_xappend(NULL, dst, bstr0("\\h"));
+ in += 1;
+ continue;
+ }
bstr_xappend(NULL, dst, (bstr){(char *)in, 1});
// Break ASS escapes with U+2060 WORD JOINER
if (escape_ass && *in == '\\')
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index 8a21ea0..4bc9e15 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -87,10 +87,14 @@ static const char *const font_mimetypes[] = {
"application/vnd.ms-opentype",
"application/x-font-ttf",
"application/x-font", // probably incorrect
+ "font/collection",
+ "font/otf",
+ "font/sfnt",
+ "font/ttf",
NULL
};
-static const char *const font_exts[] = {".ttf", ".ttc", ".otf", NULL};
+static const char *const font_exts[] = {".ttf", ".ttc", ".otf", ".otc", NULL};
static bool attachment_is_font(struct mp_log *log, struct demux_attachment *f)
{
diff --git a/test/gl_video.c b/test/gl_video.c
index 97fee94..a2d2577 100644
--- a/test/gl_video.c
+++ b/test/gl_video.c
@@ -1,5 +1,5 @@
#include "test_helpers.h"
-#include "video/out/opengl/video.h"
+#include "video/out/gpu/video.h"
static void test_scale_ambient_lux_limits(void **state) {
float x;
diff --git a/video/csputils.c b/video/csputils.c
index d9a5c29..f02a4ca 100644
--- a/video/csputils.c
+++ b/video/csputils.c
@@ -66,6 +66,7 @@ const struct m_opt_choice_alternatives mp_csp_prim_names[] = {
{"prophoto", MP_CSP_PRIM_PRO_PHOTO},
{"cie1931", MP_CSP_PRIM_CIE_1931},
{"dci-p3", MP_CSP_PRIM_DCI_P3},
+ {"display-p3", MP_CSP_PRIM_DISPLAY_P3},
{"v-gamut", MP_CSP_PRIM_V_GAMUT},
{"s-gamut", MP_CSP_PRIM_S_GAMUT},
{0}
@@ -363,6 +364,7 @@ struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc)
d50 = {0.34577, 0.35850},
d65 = {0.31271, 0.32902},
c = {0.31006, 0.31616},
+ dci = {0.31400, 0.35100},
e = {1.0/3.0, 1.0/3.0};
switch (spc) {
@@ -432,13 +434,14 @@ struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc)
.blue = {0.1666, 0.0089},
.white = e
};
- // From SMPTE RP 431-2
+ // From SMPTE RP 431-2 and 432-1
case MP_CSP_PRIM_DCI_P3:
+ case MP_CSP_PRIM_DISPLAY_P3:
return (struct mp_csp_primaries) {
.red = {0.680, 0.320},
.green = {0.265, 0.690},
.blue = {0.150, 0.060},
- .white = d65
+ .white = spc == MP_CSP_PRIM_DCI_P3 ? dci : d65
};
// From Panasonic VARICAM reference manual
case MP_CSP_PRIM_V_GAMUT:
diff --git a/video/csputils.h b/video/csputils.h
index 246dfdc..441f2b0 100644
--- a/video/csputils.h
+++ b/video/csputils.h
@@ -64,6 +64,7 @@ enum mp_csp_prim {
MP_CSP_PRIM_PRO_PHOTO,
MP_CSP_PRIM_CIE_1931,
MP_CSP_PRIM_DCI_P3,
+ MP_CSP_PRIM_DISPLAY_P3,
MP_CSP_PRIM_V_GAMUT,
MP_CSP_PRIM_S_GAMUT,
MP_CSP_PRIM_COUNT
diff --git a/video/d3d.c b/video/d3d.c
new file mode 100644
index 0000000..8f04dcd
--- /dev/null
+++ b/video/d3d.c
@@ -0,0 +1,287 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+
+#include "config.h"
+
+#include <libavcodec/avcodec.h>
+
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_d3d11va.h>
+
+#if HAVE_D3D9_HWACCEL
+#include <libavutil/hwcontext_dxva2.h>
+#endif
+
+#include "common/common.h"
+#include "common/av_common.h"
+#include "video/fmt-conversion.h"
+#include "video/hwdec.h"
+#include "video/mp_image.h"
+#include "video/mp_image_pool.h"
+#include "osdep/windows_utils.h"
+
+#include "d3d.h"
+
+HMODULE d3d11_dll, d3d9_dll, dxva2_dll;
+PFN_D3D11_CREATE_DEVICE d3d11_D3D11CreateDevice;
+
+static pthread_once_t d3d_load_once = PTHREAD_ONCE_INIT;
+
+#if !HAVE_UWP
+static void d3d_do_load(void)
+{
+ d3d11_dll = LoadLibrary(L"d3d11.dll");
+ d3d9_dll = LoadLibrary(L"d3d9.dll");
+ dxva2_dll = LoadLibrary(L"dxva2.dll");
+
+ if (d3d11_dll) {
+ d3d11_D3D11CreateDevice =
+ (void *)GetProcAddress(d3d11_dll, "D3D11CreateDevice");
+ }
+}
+#else
+static void d3d_do_load(void)
+{
+
+ d3d11_D3D11CreateDevice = D3D11CreateDevice;
+}
+#endif
+
+void d3d_load_dlls(void)
+{
+ pthread_once(&d3d_load_once, d3d_do_load);
+}
+
+// Test if Direct3D11 can be used by us. Basically, this prevents trying to use
+// D3D11 on Win7, and then failing somewhere in the process.
+bool d3d11_check_decoding(ID3D11Device *dev)
+{
+ HRESULT hr;
+ // We assume that NV12 is always supported, if hw decoding is supported at
+ // all.
+ UINT supported = 0;
+ hr = ID3D11Device_CheckFormatSupport(dev, DXGI_FORMAT_NV12, &supported);
+ return !FAILED(hr) && (supported & D3D11_BIND_DECODER);
+}
+
+static void d3d11_refine_hwframes(AVBufferRef *hw_frames_ctx)
+{
+ AVHWFramesContext *fctx = (void *)hw_frames_ctx->data;
+
+ if (fctx->format == AV_PIX_FMT_D3D11) {
+ AVD3D11VAFramesContext *hwctx = fctx->hwctx;
+
+ // According to hwcontex_d3d11va.h, yuv420p means DXGI_FORMAT_420_OPAQUE,
+ // which has no shader support.
+ if (fctx->sw_format != AV_PIX_FMT_YUV420P)
+ hwctx->BindFlags |= D3D11_BIND_SHADER_RESOURCE;
+ }
+}
+
+AVBufferRef *d3d11_wrap_device_ref(ID3D11Device *device)
+{
+ AVBufferRef *device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA);
+ if (!device_ref)
+ return NULL;
+
+ AVHWDeviceContext *ctx = (void *)device_ref->data;
+ AVD3D11VADeviceContext *hwctx = ctx->hwctx;
+
+ ID3D11Device_AddRef(device);
+ hwctx->device = device;
+
+ if (av_hwdevice_ctx_init(device_ref) < 0)
+ av_buffer_unref(&device_ref);
+
+ return device_ref;
+}
+
+static void d3d11_complete_image_params(struct mp_image *img)
+{
+ AVHWFramesContext *hw_frames = (void *)img->hwctx->data;
+
+ // According to hwcontex_d3d11va.h, this means DXGI_FORMAT_420_OPAQUE.
+ img->params.hw_flags = hw_frames->sw_format == AV_PIX_FMT_YUV420P
+ ? MP_IMAGE_HW_FLAG_OPAQUE : 0;
+
+ if (img->params.hw_subfmt == IMGFMT_NV12)
+ mp_image_setfmt(img, IMGFMT_D3D11NV12);
+}
+
+static struct AVBufferRef *d3d11_create_standalone(struct mpv_global *global,
+ struct mp_log *plog, struct hwcontext_create_dev_params *params)
+{
+ ID3D11Device *device = NULL;
+ HRESULT hr;
+
+ d3d_load_dlls();
+ if (!d3d11_D3D11CreateDevice) {
+ mp_err(plog, "Failed to load D3D11 library\n");
+ return NULL;
+ }
+
+ hr = d3d11_D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
+ D3D11_CREATE_DEVICE_VIDEO_SUPPORT, NULL, 0,
+ D3D11_SDK_VERSION, &device, NULL, NULL);
+ if (FAILED(hr)) {
+ mp_err(plog, "Failed to create D3D11 Device: %s\n",
+ mp_HRESULT_to_str(hr));
+ return NULL;
+ }
+
+ AVBufferRef *avref = d3d11_wrap_device_ref(device);
+ ID3D11Device_Release(device);
+ if (!avref)
+ mp_err(plog, "Failed to allocate AVHWDeviceContext.\n");
+
+ return avref;
+}
+
+const struct hwcontext_fns hwcontext_fns_d3d11 = {
+ .av_hwdevice_type = AV_HWDEVICE_TYPE_D3D11VA,
+ .complete_image_params = d3d11_complete_image_params,
+ .refine_hwframes = d3d11_refine_hwframes,
+ .create_dev = d3d11_create_standalone,
+};
+
+#if HAVE_D3D9_HWACCEL
+
+#define DXVA2API_USE_BITFIELDS
+#include <libavutil/common.h>
+
+#include <libavutil/hwcontext_dxva2.h>
+
+static void d3d9_free_av_device_ref(AVHWDeviceContext *ctx)
+{
+ AVDXVA2DeviceContext *hwctx = ctx->hwctx;
+
+ if (hwctx->devmgr)
+ IDirect3DDeviceManager9_Release(hwctx->devmgr);
+}
+
+AVBufferRef *d3d9_wrap_device_ref(IDirect3DDevice9 *device)
+{
+ HRESULT hr;
+
+ d3d_load_dlls();
+ if (!dxva2_dll)
+ return NULL;
+
+ HRESULT (WINAPI *DXVA2CreateDirect3DDeviceManager9)(UINT *, IDirect3DDeviceManager9 **) =
+ (void *)GetProcAddress(dxva2_dll, "DXVA2CreateDirect3DDeviceManager9");
+ if (!DXVA2CreateDirect3DDeviceManager9)
+ return NULL;
+
+ AVBufferRef *device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DXVA2);
+ if (!device_ref)
+ return NULL;
+
+ AVHWDeviceContext *ctx = (void *)device_ref->data;
+ AVDXVA2DeviceContext *hwctx = ctx->hwctx;
+
+ UINT reset_token = 0;
+ hr = DXVA2CreateDirect3DDeviceManager9(&reset_token, &hwctx->devmgr);
+ if (FAILED(hr))
+ goto fail;
+
+ IDirect3DDeviceManager9_ResetDevice(hwctx->devmgr, device, reset_token);
+ if (FAILED(hr))
+ goto fail;
+
+ ctx->free = d3d9_free_av_device_ref;
+
+ if (av_hwdevice_ctx_init(device_ref) < 0)
+ goto fail;
+
+ return device_ref;
+
+fail:
+ d3d9_free_av_device_ref(ctx);
+ av_buffer_unref(&device_ref);
+ return NULL;
+}
+
+static struct AVBufferRef *d3d9_create_standalone(struct mpv_global *global,
+ struct mp_log *plog, struct hwcontext_create_dev_params *params)
+{
+ d3d_load_dlls();
+ if (!d3d9_dll || !dxva2_dll) {
+ mp_err(plog, "Failed to load D3D9 library\n");
+ return NULL;
+ }
+
+ HRESULT (WINAPI *Direct3DCreate9Ex)(UINT, IDirect3D9Ex **) =
+ (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
+ if (!Direct3DCreate9Ex) {
+ mp_err(plog, "Failed to locate Direct3DCreate9Ex\n");
+ return NULL;
+ }
+
+ IDirect3D9Ex *d3d9ex = NULL;
+ HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
+ if (FAILED(hr)) {
+ mp_err(plog, "Failed to create IDirect3D9Ex object\n");
+ return NULL;
+ }
+
+ UINT adapter = D3DADAPTER_DEFAULT;
+ D3DDISPLAYMODEEX modeex = {0};
+ IDirect3D9Ex_GetAdapterDisplayModeEx(d3d9ex, adapter, &modeex, NULL);
+
+ D3DPRESENT_PARAMETERS present_params = {
+ .Windowed = TRUE,
+ .BackBufferWidth = 640,
+ .BackBufferHeight = 480,
+ .BackBufferCount = 0,
+ .BackBufferFormat = modeex.Format,
+ .SwapEffect = D3DSWAPEFFECT_DISCARD,
+ .Flags = D3DPRESENTFLAG_VIDEO,
+ };
+
+ IDirect3DDevice9Ex *exdev = NULL;
+ hr = IDirect3D9Ex_CreateDeviceEx(d3d9ex, adapter,
+ D3DDEVTYPE_HAL,
+ GetShellWindow(),
+ D3DCREATE_SOFTWARE_VERTEXPROCESSING |
+ D3DCREATE_MULTITHREADED |
+ D3DCREATE_FPU_PRESERVE,
+ &present_params,
+ NULL,
+ &exdev);
+ IDirect3D9_Release(d3d9ex);
+ if (FAILED(hr)) {
+ mp_err(plog, "Failed to create Direct3D device: %s\n",
+ mp_HRESULT_to_str(hr));
+ return NULL;
+ }
+
+ AVBufferRef *avref = d3d9_wrap_device_ref((IDirect3DDevice9 *)exdev);
+ IDirect3DDevice9Ex_Release(exdev);
+ if (!avref)
+ mp_err(plog, "Failed to allocate AVHWDeviceContext.\n");
+
+ return avref;
+}
+
+const struct hwcontext_fns hwcontext_fns_dxva2 = {
+ .av_hwdevice_type = AV_HWDEVICE_TYPE_DXVA2,
+ .create_dev = d3d9_create_standalone,
+};
+
+#endif /* HAVE_D3D9_HWACCEL */
diff --git a/video/d3d.h b/video/d3d.h
new file mode 100644
index 0000000..0058905
--- /dev/null
+++ b/video/d3d.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPV_DECODE_D3D_H
+#define MPV_DECODE_D3D_H
+
+#include <windows.h>
+#include <d3d11.h>
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+// Must call d3d_load_dlls() before accessing. Once this is done, the DLLs
+// remain loaded forever.
+extern HMODULE d3d11_dll, d3d9_dll, dxva2_dll;
+extern PFN_D3D11_CREATE_DEVICE d3d11_D3D11CreateDevice;
+
+void d3d_load_dlls(void);
+
+bool d3d11_check_decoding(ID3D11Device *dev);
+
+struct AVBufferRef;
+struct IDirect3DDevice9;
+
+struct AVBufferRef *d3d11_wrap_device_ref(ID3D11Device *device);
+struct AVBufferRef *d3d9_wrap_device_ref(struct IDirect3DDevice9 *device);
+
+#endif
diff --git a/video/decode/d3d.c b/video/decode/d3d.c
deleted file mode 100644
index 36d914d..0000000
--- a/video/decode/d3d.c
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <pthread.h>
-
-#include <libavcodec/avcodec.h>
-
-#include "config.h"
-
-#include "lavc.h"
-#include "common/common.h"
-#include "common/av_common.h"
-#include "video/fmt-conversion.h"
-#include "video/mp_image.h"
-#include "video/mp_image_pool.h"
-#include "osdep/windows_utils.h"
-
-#include "d3d.h"
-
-#if !HAVE_D3D_HWACCEL_NEW
-
-// define all the GUIDs used directly here, to avoid problems with inconsistent
-// dxva2api.h versions in mingw-w64 and different MSVC version
-#include <guiddef.h>
-#include <cguid.h>
-DEFINE_GUID(DXVA2_ModeMPEG2_VLD, 0xee27417f, 0x5e28, 0x4e65, 0xbe, 0xea, 0x1d, 0x26, 0xb5, 0x08, 0xad, 0xc9);
-DEFINE_GUID(DXVA2_ModeMPEG2and1_VLD, 0x86695f12, 0x340e, 0x4f04, 0x9f, 0xd3, 0x92, 0x53, 0xdd, 0x32, 0x74, 0x60);
-
-DEFINE_GUID(DXVA2_ModeH264_E, 0x1b81be68, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
-DEFINE_GUID(DXVA2_ModeH264_F, 0x1b81be69, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
-DEFINE_GUID(DXVA_ModeH264_VLD_WithFMOASO_NoFGT, 0xd5f04ff9, 0x3418, 0x45d8, 0x95, 0x61, 0x32, 0xa7, 0x6a, 0xae, 0x2d, 0xdd);
-DEFINE_GUID(DXVA_Intel_H264_NoFGT_ClearVideo, 0x604F8E68, 0x4951, 0x4c54, 0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6);
-DEFINE_GUID(DXVA_ModeH264_VLD_NoFGT_Flash, 0x4245F676, 0x2BBC, 0x4166, 0xa0, 0xBB, 0x54, 0xE7, 0xB8, 0x49, 0xC3, 0x80);
-
-DEFINE_GUID(DXVA2_ModeVC1_D, 0x1b81beA3, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
-DEFINE_GUID(DXVA2_ModeVC1_D2010, 0x1b81beA4, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5); // August 2010 update
-
-DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main, 0x5b11d51b, 0x2f4c, 0x4452, 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0);
-DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main10, 0x107af0e0, 0xef1a, 0x4d19, 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13);
-
-DEFINE_GUID(DXVA2_ModeVP9_VLD_Profile0, 0x463707f8, 0xa1d0, 0x4585, 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e);
-
-DEFINE_GUID(DXVA2_NoEncrypt, 0x1b81beD0, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
-
-static const int PROF_MPEG2_MAIN[] = {FF_PROFILE_MPEG2_SIMPLE,
- FF_PROFILE_MPEG2_MAIN, 0};
-static const int PROF_H264_HIGH[] = {FF_PROFILE_H264_CONSTRAINED_BASELINE,
- FF_PROFILE_H264_MAIN,
- FF_PROFILE_H264_HIGH, 0};
-static const int PROF_HEVC_MAIN[] = {FF_PROFILE_HEVC_MAIN, 0};
-static const int PROF_HEVC_MAIN10[] = {FF_PROFILE_HEVC_MAIN,
- FF_PROFILE_HEVC_MAIN_10, 0};
-
-struct d3dva_mode {
- const GUID *guid;
- const char *name;
- enum AVCodecID codec;
- const int *profiles; // NULL or ends with 0
-};
-
-#define MODE2(id) &MP_CONCAT(DXVA2_Mode, id), # id
-#define MODE(id) &MP_CONCAT(DXVA_, id), # id
-// Preferred modes must come first
-static const struct d3dva_mode d3dva_modes[] = {
- // MPEG-1/2
- {MODE2(MPEG2_VLD), AV_CODEC_ID_MPEG2VIDEO, PROF_MPEG2_MAIN},
- {MODE2(MPEG2and1_VLD), AV_CODEC_ID_MPEG2VIDEO, PROF_MPEG2_MAIN},
- {MODE2(MPEG2and1_VLD), AV_CODEC_ID_MPEG1VIDEO},
-
- // H.264
- {MODE2(H264_F), AV_CODEC_ID_H264, PROF_H264_HIGH},
- {MODE2(H264_E), AV_CODEC_ID_H264, PROF_H264_HIGH},
- {MODE (Intel_H264_NoFGT_ClearVideo), AV_CODEC_ID_H264, PROF_H264_HIGH},
- {MODE (ModeH264_VLD_WithFMOASO_NoFGT), AV_CODEC_ID_H264, PROF_H264_HIGH},
- {MODE (ModeH264_VLD_NoFGT_Flash), AV_CODEC_ID_H264, PROF_H264_HIGH},
-
- // VC-1 / WMV3
- {MODE2(VC1_D), AV_CODEC_ID_VC1},
- {MODE2(VC1_D), AV_CODEC_ID_WMV3},
- {MODE2(VC1_D2010), AV_CODEC_ID_VC1},
- {MODE2(VC1_D2010), AV_CODEC_ID_WMV3},
-
- // HEVC
- {MODE2(HEVC_VLD_Main), AV_CODEC_ID_HEVC, PROF_HEVC_MAIN},
- {MODE2(HEVC_VLD_Main10), AV_CODEC_ID_HEVC, PROF_HEVC_MAIN10},
-
- // VP9
- {MODE2(VP9_VLD_Profile0), AV_CODEC_ID_VP9},
-};
-#undef MODE
-#undef MODE2
-
-#endif
-
-HMODULE d3d11_dll, d3d9_dll, dxva2_dll;
-PFN_D3D11_CREATE_DEVICE d3d11_D3D11CreateDevice;
-
-static pthread_once_t d3d_load_once = PTHREAD_ONCE_INIT;
-
-#if !HAVE_UWP
-static void d3d_do_load(void)
-{
- d3d11_dll = LoadLibrary(L"d3d11.dll");
- d3d9_dll = LoadLibrary(L"d3d9.dll");
- dxva2_dll = LoadLibrary(L"dxva2.dll");
-
- if (d3d11_dll) {
- d3d11_D3D11CreateDevice =
- (void *)GetProcAddress(d3d11_dll, "D3D11CreateDevice");
- }
-}
-#else
-static void d3d_do_load(void)
-{
-
- d3d11_D3D11CreateDevice = D3D11CreateDevice;
-}
-#endif
-
-void d3d_load_dlls(void)
-{
- pthread_once(&d3d_load_once, d3d_do_load);
-}
-
-
-// Test if Direct3D11 can be used by us. Basically, this prevents trying to use
-// D3D11 on Win7, and then failing somewhere in the process.
-bool d3d11_check_decoding(ID3D11Device *dev)
-{
- HRESULT hr;
- // We assume that NV12 is always supported, if hw decoding is supported at
- // all.
- UINT supported = 0;
- hr = ID3D11Device_CheckFormatSupport(dev, DXGI_FORMAT_NV12, &supported);
- return !FAILED(hr) && (supported & D3D11_BIND_DECODER);
-}
-
-#if !HAVE_D3D_HWACCEL_NEW
-
-int d3d_probe_codec(const char *codec)
-{
- enum AVCodecID codecid = mp_codec_to_av_codec_id(codec);
- for (int i = 0; i < MP_ARRAY_SIZE(d3dva_modes); i++) {
- const struct d3dva_mode *mode = &d3dva_modes[i];
- if (mode->codec == codecid)
- return 0;
- }
- return HWDEC_ERR_NO_CODEC;
-}
-
-static bool profile_compatible(const struct d3dva_mode *mode, int profile)
-{
- if (!mode->profiles)
- return true;
-
- for (int i = 0; mode->profiles[i]; i++){
- if(mode->profiles[i] == profile)
- return true;
- }
- return false;
-}
-
-static bool mode_supported(const struct d3dva_mode *mode,
- const GUID *device_modes, UINT n_modes)
-{
- for (int i = 0; i < n_modes; i++) {
- if (IsEqualGUID(mode->guid, &device_modes[i]))
- return true;
- }
- return false;
-}
-
-struct d3d_decoder_fmt d3d_select_decoder_mode(
- struct lavc_ctx *s, const GUID *device_guids, UINT n_guids,
- const struct d3d_decoded_format *formats, int n_formats,
- bool (*test_fmt_cb)(struct lavc_ctx *s, const GUID *guid,
- const struct d3d_decoded_format *fmt))
-{
- struct d3d_decoder_fmt fmt = {
- .guid = &GUID_NULL,
- .format = NULL,
- };
-
- // this has the right bit-depth, but is unfortunately not the native format
- int sw_img_fmt = pixfmt2imgfmt(s->avctx->sw_pix_fmt);
- if (sw_img_fmt == IMGFMT_NONE)
- return fmt;
-
- int depth = IMGFMT_RGB_DEPTH(sw_img_fmt);
-
- for (int i = 0; i < MP_ARRAY_SIZE(d3dva_modes); i++) {
- const struct d3dva_mode *mode = &d3dva_modes[i];
- if (mode->codec == s->avctx->codec_id &&
- profile_compatible(mode, s->avctx->profile) &&
- mode_supported(mode, device_guids, n_guids)) {
-
- for (int n = 0; n < n_formats; n++) {
- const struct d3d_decoded_format *format = &formats[n];
-
- if (depth <= format->depth && test_fmt_cb(s, mode->guid, format))
- {
- MP_VERBOSE(s, "Selecting %s ",
- d3d_decoder_guid_to_desc(mode->guid));
- if (format->dxfmt >= (1 << 16)) {
- MP_VERBOSE(s, "%s\n", mp_tag_str(format->dxfmt));
- } else {
- MP_VERBOSE(s, "%d\n", (int)format->dxfmt);
- }
-
- fmt.guid = mode->guid;
- fmt.format = format;
- return fmt;
- }
- }
- }
- }
- return fmt;
-}
-
-char *d3d_decoder_guid_to_desc_buf(char *buf, size_t buf_size,
- const GUID *mode_guid)
-{
- const char *name = "<unknown>";
- for (int i = 0; i < MP_ARRAY_SIZE(d3dva_modes); i++) {
- const struct d3dva_mode *mode = &d3dva_modes[i];
- if (IsEqualGUID(mode->guid, mode_guid)) {
- name = mode->name;
- break;
- }
- }
- snprintf(buf, buf_size, "%s %s", mp_GUID_to_str(mode_guid), name);
- return buf;
-}
-
-void d3d_surface_align(struct lavc_ctx *s, int *w, int *h)
-{
- int alignment = 16;
- switch (s->avctx->codec_id) {
- // decoding MPEG-2 requires additional alignment on some Intel GPUs, but it
- // causes issues for H.264 on certain AMD GPUs.....
- case AV_CODEC_ID_MPEG2VIDEO:
- alignment = 32;
- break;
- // the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure
- // all coding features have enough room to work with
- case AV_CODEC_ID_HEVC:
- alignment = 128;
- break;
- }
- *w = FFALIGN(*w, alignment);
- *h = FFALIGN(*h, alignment);
-}
-
-unsigned d3d_decoder_config_score(struct lavc_ctx *s,
- GUID *guidConfigBitstreamEncryption,
- UINT ConfigBitstreamRaw)
-{
- unsigned score = 0;
- if (ConfigBitstreamRaw == 1) {
- score = 1;
- } else if (s->avctx->codec_id == AV_CODEC_ID_H264
- && ConfigBitstreamRaw == 2) {
- score = 2;
- } else {
- return 0;
- }
-
- if (IsEqualGUID(guidConfigBitstreamEncryption, &DXVA2_NoEncrypt))
- score += 16;
-
- return score;
-}
-
-BOOL is_clearvideo(const GUID *mode_guid)
-{
- return IsEqualGUID(mode_guid, &DXVA_Intel_H264_NoFGT_ClearVideo);
-}
-
-void copy_nv12(struct mp_image *dest, uint8_t *src_bits,
- unsigned src_pitch, unsigned surf_height)
-{
- struct mp_image buf = {0};
- mp_image_setfmt(&buf, dest->imgfmt);
- mp_image_set_size(&buf, dest->w, dest->h);
-
- buf.planes[0] = src_bits;
- buf.stride[0] = src_pitch;
- buf.planes[1] = src_bits + src_pitch * surf_height;
- buf.stride[1] = src_pitch;
- mp_image_copy_gpu(dest, &buf);
-}
-
-static int get_dxgi_mpfmt(DWORD dxgi_fmt)
-{
- switch (dxgi_fmt) {
- case DXGI_FORMAT_NV12: return IMGFMT_NV12;
- case DXGI_FORMAT_P010: return IMGFMT_P010;
- case DXGI_FORMAT_P016: return IMGFMT_P010;
- }
- return 0;
-}
-
-struct mp_image *d3d11_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool)
-{
- HRESULT hr;
- ID3D11Device *device = ctx->ctx;
-
- if (mpi->imgfmt != IMGFMT_D3D11VA && mpi->imgfmt != IMGFMT_D3D11NV12)
- return NULL;
-
- ID3D11Texture2D *texture = (void *)mpi->planes[0];
- int subindex = (intptr_t)mpi->planes[1];
- if (!texture)
- return NULL;
-
- D3D11_TEXTURE2D_DESC tex_desc;
- ID3D11Texture2D_GetDesc(texture, &tex_desc);
- int mpfmt = get_dxgi_mpfmt(tex_desc.Format);
- if (!mpfmt)
- return NULL;
-
- // create staging texture shared with the CPU with mostly the same
- // parameters as the source texture
- tex_desc.MipLevels = 1;
- tex_desc.MiscFlags = 0;
- tex_desc.ArraySize = 1;
- tex_desc.Usage = D3D11_USAGE_STAGING;
- tex_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
- tex_desc.BindFlags = 0;
- ID3D11Texture2D *staging = NULL;
- hr = ID3D11Device_CreateTexture2D(device, &tex_desc, NULL, &staging);
- if (FAILED(hr))
- return NULL;
-
- bool ok = false;
- struct mp_image *sw_img = NULL;
- ID3D11DeviceContext *device_ctx = NULL;
- ID3D11Device_GetImmediateContext(device, &device_ctx);
-
- // copy to the staging texture
- ID3D11DeviceContext_CopySubresourceRegion(
- device_ctx,
- (ID3D11Resource *)staging, 0, 0, 0, 0,
- (ID3D11Resource *)texture, subindex, NULL);
-
- sw_img = mp_image_pool_get(swpool, mpfmt, tex_desc.Width, tex_desc.Height);
- if (!sw_img)
- goto done;
-
- // copy staging texture to the cpu mp_image
- D3D11_MAPPED_SUBRESOURCE lock;
- hr = ID3D11DeviceContext_Map(device_ctx, (ID3D11Resource *)staging,
- 0, D3D11_MAP_READ, 0, &lock);
- if (FAILED(hr))
- goto done;
- copy_nv12(sw_img, lock.pData, lock.RowPitch, tex_desc.Height);
- ID3D11DeviceContext_Unmap(device_ctx, (ID3D11Resource *)staging, 0);
-
- mp_image_set_size(sw_img, mpi->w, mpi->h);
- mp_image_copy_attributes(sw_img, mpi);
- ok = true;
-
-done:
- ID3D11Texture2D_Release(staging);
- ID3D11DeviceContext_Release(device_ctx);
- if (!ok)
- mp_image_unrefp(&sw_img);
- return sw_img;
-}
-
-// Dummies for simpler compat.
-AVBufferRef *d3d11_wrap_device_ref(ID3D11Device *device) { return NULL; }
-AVBufferRef *d3d9_wrap_device_ref(struct IDirect3DDevice9 *device) { return NULL; }
-
-#else /* !HAVE_D3D_HWACCEL_NEW */
-
-#include <libavutil/hwcontext.h>
-#include <libavutil/hwcontext_d3d11va.h>
-
-#if HAVE_D3D9_HWACCEL
-#include <libavutil/hwcontext_dxva2.h>
-#endif
-
-void d3d_hwframes_refine(struct lavc_ctx *ctx, AVBufferRef *hw_frames_ctx)
-{
- AVHWFramesContext *fctx = (void *)hw_frames_ctx->data;
-
- int alignment = 16;
- switch (ctx->avctx->codec_id) {
- // decoding MPEG-2 requires additional alignment on some Intel GPUs, but it
- // causes issues for H.264 on certain AMD GPUs.....
- case AV_CODEC_ID_MPEG2VIDEO:
- alignment = 32;
- break;
- // the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure
- // all coding features have enough room to work with
- case AV_CODEC_ID_HEVC:
- alignment = 128;
- break;
- }
- fctx->width = FFALIGN(fctx->width, alignment);
- fctx->height = FFALIGN(fctx->height, alignment);
-
-#if HAVE_D3D9_HWACCEL
- if (fctx->format == AV_PIX_FMT_DXVA2_VLD) {
- AVDXVA2FramesContext *hwctx = fctx->hwctx;
-
- hwctx->surface_type = DXVA2_VideoDecoderRenderTarget;
- }
-#endif
-
- if (fctx->format == AV_PIX_FMT_D3D11) {
- AVD3D11VAFramesContext *hwctx = fctx->hwctx;
-
- hwctx->BindFlags |= D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE;
- }
-}
-
-AVBufferRef *d3d11_wrap_device_ref(ID3D11Device *device)
-{
- AVBufferRef *device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA);
- if (!device_ref)
- return NULL;
-
- AVHWDeviceContext *ctx = (void *)device_ref->data;
- AVD3D11VADeviceContext *hwctx = ctx->hwctx;
-
- ID3D11Device_AddRef(device);
- hwctx->device = device;
-
- if (av_hwdevice_ctx_init(device_ref) < 0)
- av_buffer_unref(&device_ref);
-
- return device_ref;
-}
-
-// Dummy for simpler compat.
-struct mp_image *d3d11_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool)
-{
- return NULL;
-}
-
-#endif /* else !HAVE_D3D_HWACCEL_NEW */
diff --git a/video/decode/d3d.h b/video/decode/d3d.h
deleted file mode 100644
index 8ae244c..0000000
--- a/video/decode/d3d.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MPV_DECODE_D3D_H
-#define MPV_DECODE_D3D_H
-
-#include <windows.h>
-#include <d3d11.h>
-
-#include <stdbool.h>
-#include <inttypes.h>
-
-struct mp_image;
-struct lavc_ctx;
-
-struct d3d_decoded_format {
- DWORD dxfmt; // D3DFORMAT or DXGI_FORMAT
- const char *name; // informational string repr. of dxfmt_decoded
- int depth; // significant bits (not full size)
- int mpfmt; // IMGFMT_ with compatible memory layout and semantics
-};
-
-struct d3d_decoder_fmt {
- const GUID *guid;
- const struct d3d_decoded_format *format;
-};
-
-// Must call d3d_load_dlls() before accessing. Once this is done, the DLLs
-// remain loaded forever.
-extern HMODULE d3d11_dll, d3d9_dll, dxva2_dll;
-extern PFN_D3D11_CREATE_DEVICE d3d11_D3D11CreateDevice;
-
-void d3d_load_dlls(void);
-
-int d3d_probe_codec(const char *codec);
-
-struct d3d_decoder_fmt d3d_select_decoder_mode(
- struct lavc_ctx *s, const GUID *device_guids, UINT n_guids,
- const struct d3d_decoded_format *formats, int n_formats,
- bool (*test_fmt_cb)(struct lavc_ctx *s, const GUID *guid,
- const struct d3d_decoded_format *fmt));
-
-char *d3d_decoder_guid_to_desc_buf(char *buf, size_t buf_size,
- const GUID *mode_guid);
-#define d3d_decoder_guid_to_desc(guid) d3d_decoder_guid_to_desc_buf((char[256]){0}, 256, (guid))
-
-void d3d_surface_align(struct lavc_ctx *s, int *w, int *h);
-unsigned d3d_decoder_config_score(struct lavc_ctx *s,
- GUID *guidConfigBitstreamEncryption,
- UINT ConfigBitstreamRaw);
-BOOL is_clearvideo(const GUID *mode_guid);
-void copy_nv12(struct mp_image *dest, uint8_t *src_bits,
- unsigned src_pitch, unsigned surf_height);
-
-bool d3d11_check_decoding(ID3D11Device *dev);
-
-struct mp_image *d3d11_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool);
-
-struct AVBufferRef;
-struct IDirect3DDevice9;
-
-void d3d_hwframes_refine(struct lavc_ctx *ctx, struct AVBufferRef *hw_frames_ctx);
-
-struct AVBufferRef *d3d11_wrap_device_ref(ID3D11Device *device);
-struct AVBufferRef *d3d9_wrap_device_ref(struct IDirect3DDevice9 *device);
-
-#endif
diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c
index 04e4282..ae0f9e2 100644
--- a/video/decode/dec_video.c
+++ b/video/decode/dec_video.c
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
@@ -229,6 +227,9 @@ static void fix_image_params(struct dec_video *d_video,
if (p.p_w <= 0 || p.p_h <= 0)
p.p_w = p.p_h = 1;
+ p.rotate = d_video->codec->rotate;
+ p.stereo_in = d_video->codec->stereo_mode;
+
if (opts->video_rotate < 0) {
p.rotate = 0;
} else {
@@ -392,6 +393,13 @@ void video_set_start(struct dec_video *d_video, double start_pts)
d_video->start_pts = start_pts;
}
+static bool is_new_segment(struct dec_video *d_video, struct demux_packet *p)
+{
+ return p->segmented &&
+ (p->start != d_video->start || p->end != d_video->end ||
+ p->codec != d_video->codec);
+}
+
void video_work(struct dec_video *d_video)
{
if (d_video->current_mpi || !d_video->vd_driver)
@@ -404,7 +412,7 @@ void video_work(struct dec_video *d_video)
return;
}
- if (d_video->packet && d_video->packet->new_segment) {
+ if (d_video->packet && is_new_segment(d_video, d_video->packet)) {
assert(!d_video->new_segment);
d_video->new_segment = d_video->packet;
d_video->packet = NULL;
@@ -474,8 +482,6 @@ void video_work(struct dec_video *d_video)
d_video->start = new_segment->start;
d_video->end = new_segment->end;
- new_segment->new_segment = false;
-
d_video->packet = new_segment;
d_video->current_state = DATA_AGAIN;
}
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 73570f8..d8d47a8 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_DEC_VIDEO_H
diff --git a/video/decode/hw_cuda.c b/video/decode/hw_cuda.c
deleted file mode 100644
index 64ee08d..0000000
--- a/video/decode/hw_cuda.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * Copyright (c) 2016 Philip Langdale <philipl@overt.org>
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-// This define and typedef prevent hwcontext_cuda.h trying to include cuda.h
-#define CUDA_VERSION 7050
-typedef void * CUcontext;
-
-#include <libavutil/hwcontext.h>
-#include <libavutil/hwcontext_cuda.h>
-
-#include "common/av_common.h"
-#include "video/fmt-conversion.h"
-#include "video/decode/lavc.h"
-
-#if !NEW_CUDA_HWACCEL
-
-static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
- const char *codec)
-{
- if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_CUDA))
- return HWDEC_ERR_NO_CTX;
- return 0;
-}
-
-static int init(struct lavc_ctx *ctx)
-{
- ctx->hwdec_priv = hwdec_devices_get(ctx->hwdec_devs, HWDEC_CUDA);
- return 0;
-}
-
-static int init_decoder(struct lavc_ctx *ctx, int w, int h)
-{
- AVCodecContext *avctx = ctx->avctx;
- struct mp_hwdec_ctx *hwctx = ctx->hwdec_priv;
-
- MP_VERBOSE(ctx, "Using old cuda API.\n");
-
- if (avctx->hw_frames_ctx) {
- MP_ERR(ctx, "hw_frames_ctx already initialised!\n");
- return -1;
- }
-
- avctx->hw_frames_ctx = av_hwframe_ctx_alloc(hwctx->av_device_ref);
- if (!avctx->hw_frames_ctx) {
- MP_ERR(ctx, "av_hwframe_ctx_alloc failed\n");
- goto error;
- }
-
- AVHWFramesContext *hwframe_ctx = (void* )avctx->hw_frames_ctx->data;
- hwframe_ctx->format = AV_PIX_FMT_CUDA;
-
- // This is proper use of the hw_frames_ctx API, but it does not work
- // (appaears to work but fails e.g. with 10 bit). The cuvid wrapper
- // does non-standard things, and it's a messy situation. This whole
- // file is actually used only with older libavcodec versions.
- /*
- hwframe_ctx->width = w;
- hwframe_ctx->height = h;
- hwframe_ctx->sw_format = avctx->sw_pix_fmt;
-
- if (av_hwframe_ctx_init(avctx->hw_frames_ctx) < 0)
- goto error;
- */
-
- return 0;
-
- error:
- av_buffer_unref(&avctx->hw_frames_ctx);
- return -1;
-}
-
-static void uninit(struct lavc_ctx *ctx)
-{
- ctx->hwdec_priv = NULL;
-}
-
-static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *img)
-{
- if (img->imgfmt == IMGFMT_CUDA)
- img->params.hw_subfmt = pixfmt2imgfmt(ctx->avctx->sw_pix_fmt);
- return img;
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_cuda_old = {
- .type = HWDEC_CUDA,
- .image_format = IMGFMT_CUDA,
- .lavc_suffix = "_cuvid",
- .probe = probe,
- .init = init,
- .uninit = uninit,
- .init_decoder = init_decoder,
- .process_image = process_image,
-};
-
-#endif
diff --git a/video/decode/hw_d3d11va.c b/video/decode/hw_d3d11va.c
deleted file mode 100644
index d191c95..0000000
--- a/video/decode/hw_d3d11va.c
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <libavcodec/d3d11va.h>
-#include <libavutil/mem.h>
-
-#include "config.h"
-
-#include "lavc.h"
-#include "common/common.h"
-#include "common/av_common.h"
-#include "osdep/windows_utils.h"
-#include "video/fmt-conversion.h"
-#include "video/mp_image_pool.h"
-#include "video/hwdec.h"
-
-#include "d3d.h"
-
-#if !HAVE_D3D_HWACCEL_NEW
-
-#define ADDITIONAL_SURFACES HWDEC_EXTRA_SURFACES
-
-struct d3d11va_decoder {
- ID3D11VideoDecoder *decoder;
- struct mp_image_pool *pool;
- ID3D11Texture2D *staging;
- int mpfmt_decoded;
-};
-
-struct priv {
- struct mp_log *log;
-
- ID3D11Device *device;
- ID3D11DeviceContext *device_ctx;
- ID3D11VideoDevice *video_dev;
- ID3D11VideoContext *video_ctx;
-
- struct d3d11va_decoder *decoder;
- struct mp_image_pool *sw_pool;
-};
-
-struct d3d11va_surface {
- ID3D11Texture2D *texture;
- ID3D11VideoDecoderOutputView *surface;
-};
-
-static void d3d11va_release_img(void *arg)
-{
- struct d3d11va_surface *surface = arg;
- if (surface->surface)
- ID3D11VideoDecoderOutputView_Release(surface->surface);
-
- if (surface->texture)
- ID3D11Texture2D_Release(surface->texture);
-
- talloc_free(surface);
-}
-
-static struct mp_image *d3d11va_new_ref(ID3D11VideoDecoderOutputView *view,
- int w, int h)
-{
- if (!view)
- return NULL;
- struct d3d11va_surface *surface = talloc_zero(NULL, struct d3d11va_surface);
-
- surface->surface = view;
- ID3D11VideoDecoderOutputView_AddRef(surface->surface);
- ID3D11VideoDecoderOutputView_GetResource(
- surface->surface, (ID3D11Resource **)&surface->texture);
-
- D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC surface_desc;
- ID3D11VideoDecoderOutputView_GetDesc(surface->surface, &surface_desc);
-
- struct mp_image *mpi =
- mp_image_new_custom_ref(NULL, surface, d3d11va_release_img);
- if (!mpi)
- abort();
-
- mp_image_setfmt(mpi, IMGFMT_D3D11VA);
- mp_image_set_size(mpi, w, h);
- mpi->planes[0] = (void *)surface->texture;
- mpi->planes[1] = (void *)(intptr_t)surface_desc.Texture2D.ArraySlice;
- mpi->planes[2] = NULL;
- mpi->planes[3] = (void *)surface->surface;
-
- return mpi;
-}
-
-static struct mp_image *d3d11va_allocate_image(struct lavc_ctx *s, int w, int h)
-{
- struct priv *p = s->hwdec_priv;
- struct mp_image *img = mp_image_pool_get_no_alloc(p->decoder->pool,
- IMGFMT_D3D11VA, w, h);
- if (!img)
- MP_ERR(p, "Failed to get free D3D11VA surface\n");
- return img;
-}
-
-static struct mp_image *d3d11va_retrieve_image(struct lavc_ctx *s,
- struct mp_image *img)
-{
- HRESULT hr;
- struct priv *p = s->hwdec_priv;
- ID3D11Texture2D *staging = p->decoder->staging;
-
- if (img->imgfmt != IMGFMT_D3D11VA)
- return img;
-
- ID3D11Texture2D *texture = (void *)img->planes[0];
- int subindex = (intptr_t)img->planes[1];
-
- if (!texture) {
- MP_ERR(p, "Failed to get Direct3D texture and surface from mp_image\n");
- return img;
- }
-
- D3D11_TEXTURE2D_DESC texture_desc;
- ID3D11Texture2D_GetDesc(texture, &texture_desc);
- if (texture_desc.Width < img->w || texture_desc.Height < img->h) {
- MP_ERR(p, "Direct3D11 texture smaller than mp_image dimensions\n");
- return img;
- }
-
- // copy to the staging texture
- ID3D11DeviceContext_CopySubresourceRegion(
- p->device_ctx,
- (ID3D11Resource *)staging, 0, 0, 0, 0,
- (ID3D11Resource *)texture, subindex, NULL);
-
- struct mp_image *sw_img = mp_image_pool_get(p->sw_pool,
- p->decoder->mpfmt_decoded,
- texture_desc.Width,
- texture_desc.Height);
- if (!sw_img) {
- MP_ERR(p, "Failed to get %s surface from CPU pool\n",
- mp_imgfmt_to_name(p->decoder->mpfmt_decoded));
- return img;
- }
-
- // copy staging texture to the cpu mp_image
- D3D11_MAPPED_SUBRESOURCE lock;
- hr = ID3D11DeviceContext_Map(p->device_ctx, (ID3D11Resource *)staging,
- 0, D3D11_MAP_READ, 0, &lock);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to map D3D11 surface: %s\n", mp_HRESULT_to_str(hr));
- talloc_free(sw_img);
- return img;
- }
- copy_nv12(sw_img, lock.pData, lock.RowPitch, texture_desc.Height);
- ID3D11DeviceContext_Unmap(p->device_ctx, (ID3D11Resource *)staging, 0);
-
- mp_image_set_size(sw_img, img->w, img->h);
- mp_image_copy_attributes(sw_img, img);
- talloc_free(img);
- return sw_img;
-}
-
-#define DFMT(name) MP_CONCAT(DXGI_FORMAT_, name), # name
-static const struct d3d_decoded_format d3d11_formats[] = {
- {DFMT(NV12), 8, IMGFMT_NV12},
- {DFMT(P010), 10, IMGFMT_P010},
- {DFMT(P016), 16, IMGFMT_P010},
-};
-#undef DFMT
-
-// Update hw_subfmt to the underlying format. Needed because AVFrame does not
-// have such an attribute, so it can't be passed through, and is updated here
-// instead. (But in the future, AVHWFramesContext could be used.)
-static struct mp_image *d3d11va_update_image_attribs(struct lavc_ctx *s,
- struct mp_image *img)
-{
- ID3D11Texture2D *texture = (void *)img->planes[0];
-
- if (!texture)
- return img;
-
- D3D11_TEXTURE2D_DESC texture_desc;
- ID3D11Texture2D_GetDesc(texture, &texture_desc);
- for (int n = 0; n < MP_ARRAY_SIZE(d3d11_formats); n++) {
- if (d3d11_formats[n].dxfmt == texture_desc.Format) {
- img->params.hw_subfmt = d3d11_formats[n].mpfmt;
- break;
- }
- }
-
- if (img->params.hw_subfmt == IMGFMT_NV12)
- mp_image_setfmt(img, IMGFMT_D3D11NV12);
-
- return img;
-}
-
-static bool d3d11_format_supported(struct lavc_ctx *s, const GUID *guid,
- const struct d3d_decoded_format *format)
-{
- struct priv *p = s->hwdec_priv;
- BOOL is_supported = FALSE;
- HRESULT hr = ID3D11VideoDevice_CheckVideoDecoderFormat(
- p->video_dev, guid, format->dxfmt, &is_supported);
- if (FAILED(hr)) {
- MP_ERR(p, "Check decoder output format %s for decoder %s: %s\n",
- format->name, d3d_decoder_guid_to_desc(guid),
- mp_HRESULT_to_str(hr));
- }
- return is_supported;
-}
-
-static void dump_decoder_info(struct lavc_ctx *s, const GUID *guid)
-{
- struct priv *p = s->hwdec_priv;
- char fmts[256] = {0};
- for (int i = 0; i < MP_ARRAY_SIZE(d3d11_formats); i++) {
- const struct d3d_decoded_format *format = &d3d11_formats[i];
- if (d3d11_format_supported(s, guid, format))
- mp_snprintf_cat(fmts, sizeof(fmts), " %s", format->name);
- }
- MP_VERBOSE(p, "%s %s\n", d3d_decoder_guid_to_desc(guid), fmts);
-}
-
-static void d3d11va_destroy_decoder(void *arg)
-{
- struct d3d11va_decoder *decoder = arg;
-
- if (decoder->decoder)
- ID3D11VideoDecoder_Release(decoder->decoder);
-
- if (decoder->staging)
- ID3D11Texture2D_Release(decoder->staging);
-}
-
-static int d3d11va_init_decoder(struct lavc_ctx *s, int w, int h)
-{
- HRESULT hr;
- int ret = -1;
- struct priv *p = s->hwdec_priv;
- TA_FREEP(&p->decoder);
-
- ID3D11Texture2D *texture = NULL;
- void *tmp = talloc_new(NULL);
-
- UINT n_guids = ID3D11VideoDevice_GetVideoDecoderProfileCount(p->video_dev);
- GUID *device_guids = talloc_array(tmp, GUID, n_guids);
- for (UINT i = 0; i < n_guids; i++) {
- GUID *guid = &device_guids[i];
- hr = ID3D11VideoDevice_GetVideoDecoderProfile(p->video_dev, i, guid);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get VideoDecoderProfile %d: %s\n",
- i, mp_HRESULT_to_str(hr));
- goto done;
- }
- dump_decoder_info(s, guid);
- }
-
- struct d3d_decoder_fmt fmt =
- d3d_select_decoder_mode(s, device_guids, n_guids,
- d3d11_formats, MP_ARRAY_SIZE(d3d11_formats),
- d3d11_format_supported);
- if (!fmt.format) {
- MP_ERR(p, "Failed to find a suitable decoder\n");
- goto done;
- }
-
- struct d3d11va_decoder *decoder = talloc_zero(tmp, struct d3d11va_decoder);
- talloc_set_destructor(decoder, d3d11va_destroy_decoder);
- decoder->mpfmt_decoded = fmt.format->mpfmt;
-
- int n_surfaces = hwdec_get_max_refs(s) + ADDITIONAL_SURFACES;
- int w_align = w, h_align = h;
- d3d_surface_align(s, &w_align, &h_align);
-
- D3D11_TEXTURE2D_DESC tex_desc = {
- .Width = w_align,
- .Height = h_align,
- .MipLevels = 1,
- .Format = fmt.format->dxfmt,
- .SampleDesc.Count = 1,
- .MiscFlags = 0,
- .ArraySize = n_surfaces,
- .Usage = D3D11_USAGE_DEFAULT,
- .BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE,
- .CPUAccessFlags = 0,
- };
- hr = ID3D11Device_CreateTexture2D(p->device, &tex_desc, NULL, &texture);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create Direct3D11 texture with %d surfaces: %s\n",
- n_surfaces, mp_HRESULT_to_str(hr));
- goto done;
- }
-
- if (s->hwdec->type == HWDEC_D3D11VA_COPY) {
- // create staging texture shared with the CPU with mostly the same
- // parameters as the above decoder-bound texture
- ID3D11Texture2D_GetDesc(texture, &tex_desc);
- tex_desc.MipLevels = 1;
- tex_desc.MiscFlags = 0;
- tex_desc.ArraySize = 1;
- tex_desc.Usage = D3D11_USAGE_STAGING;
- tex_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
- tex_desc.BindFlags = 0;
- hr = ID3D11Device_CreateTexture2D(p->device, &tex_desc, NULL,
- &decoder->staging);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create staging texture: %s\n",
- mp_HRESULT_to_str(hr));
- goto done;
- }
- }
-
- // pool to hold the mp_image wrapped surfaces
- decoder->pool = talloc_steal(decoder, mp_image_pool_new(n_surfaces));
- // array of the same surfaces (needed by ffmpeg)
- ID3D11VideoDecoderOutputView **surfaces =
- talloc_array_ptrtype(decoder->pool, surfaces, n_surfaces);
-
- D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC view_desc = {
- .DecodeProfile = *fmt.guid,
- .ViewDimension = D3D11_VDOV_DIMENSION_TEXTURE2D,
- };
- for (int i = 0; i < n_surfaces; i++) {
- ID3D11VideoDecoderOutputView **surface = &surfaces[i];
- view_desc.Texture2D.ArraySlice = i;
- hr = ID3D11VideoDevice_CreateVideoDecoderOutputView(
- p->video_dev, (ID3D11Resource *)texture, &view_desc, surface);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed getting decoder output view %d: %s\n",
- i, mp_HRESULT_to_str(hr));
- goto done;
- }
- struct mp_image *img = d3d11va_new_ref(*surface, w, h);
- ID3D11VideoDecoderOutputView_Release(*surface); // transferred to img
- if (!img) {
- MP_ERR(p, "Failed to create D3D11VA image %d\n", i);
- goto done;
- }
- mp_image_pool_add(decoder->pool, img); // transferred to pool
- }
-
- D3D11_VIDEO_DECODER_DESC decoder_desc = {
- .Guid = *fmt.guid,
- .SampleWidth = w,
- .SampleHeight = h,
- .OutputFormat = fmt.format->dxfmt,
- };
- UINT n_cfg;
- hr = ID3D11VideoDevice_GetVideoDecoderConfigCount(p->video_dev,
- &decoder_desc, &n_cfg);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get number of decoder configurations: %s)",
- mp_HRESULT_to_str(hr));
- goto done;
- }
-
- // pick the config with the highest score
- D3D11_VIDEO_DECODER_CONFIG *decoder_config =
- talloc_zero(decoder, D3D11_VIDEO_DECODER_CONFIG);
- unsigned max_score = 0;
- for (UINT i = 0; i < n_cfg; i++) {
- D3D11_VIDEO_DECODER_CONFIG cfg;
- hr = ID3D11VideoDevice_GetVideoDecoderConfig(p->video_dev,
- &decoder_desc,
- i, &cfg);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get decoder config %d: %s\n",
- i, mp_HRESULT_to_str(hr));
- goto done;
- }
- unsigned score = d3d_decoder_config_score(
- s, &cfg.guidConfigBitstreamEncryption, cfg.ConfigBitstreamRaw);
- if (score > max_score) {
- max_score = score;
- *decoder_config = cfg;
- }
- }
- if (!max_score) {
- MP_ERR(p, "Failed to find a suitable decoder configuration\n");
- goto done;
- }
-
- hr = ID3D11VideoDevice_CreateVideoDecoder(p->video_dev, &decoder_desc,
- decoder_config,
- &decoder->decoder);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create video decoder: %s\n",
- mp_HRESULT_to_str(hr));
- goto done;
- }
-
- struct AVD3D11VAContext *avd3d11va_ctx = s->avctx->hwaccel_context;
- avd3d11va_ctx->decoder = decoder->decoder;
- avd3d11va_ctx->video_context = p->video_ctx;
- avd3d11va_ctx->cfg = decoder_config;
- avd3d11va_ctx->surface_count = n_surfaces;
- avd3d11va_ctx->surface = surfaces;
- avd3d11va_ctx->workaround = is_clearvideo(fmt.guid) ?
- FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO : 0;
-
- p->decoder = talloc_steal(NULL, decoder);
- ret = 0;
-done:
- // still referenced by pool images / surfaces
- if (texture)
- ID3D11Texture2D_Release(texture);
-
- talloc_free(tmp);
- return ret;
-}
-
-static void destroy_device(struct lavc_ctx *s)
-{
- struct priv *p = s->hwdec_priv;
-
- if (p->device)
- ID3D11Device_Release(p->device);
-
- if (p->device_ctx)
- ID3D11DeviceContext_Release(p->device_ctx);
-}
-
-static bool create_device(struct lavc_ctx *s, BOOL thread_safe)
-{
- HRESULT hr;
- struct priv *p = s->hwdec_priv;
-
- if (!d3d11_dll) {
- MP_ERR(p, "Failed to load D3D11 library\n");
- return false;
- }
-
- PFN_D3D11_CREATE_DEVICE CreateDevice =
- (void *)GetProcAddress(d3d11_dll, "D3D11CreateDevice");
- if (!CreateDevice) {
- MP_ERR(p, "Failed to get D3D11CreateDevice symbol from DLL: %s\n",
- mp_LastError_to_str());
- return false;
- }
-
- hr = CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
- D3D11_CREATE_DEVICE_VIDEO_SUPPORT, NULL, 0,
- D3D11_SDK_VERSION, &p->device, NULL, &p->device_ctx);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create D3D11 Device: %s\n",
- mp_HRESULT_to_str(hr));
- return false;
- }
-
- ID3D10Multithread *multithread;
- hr = ID3D11Device_QueryInterface(p->device, &IID_ID3D10Multithread,
- (void **)&multithread);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get Multithread interface: %s\n",
- mp_HRESULT_to_str(hr));
- return false;
- }
- ID3D10Multithread_SetMultithreadProtected(multithread, thread_safe);
- ID3D10Multithread_Release(multithread);
- return true;
-}
-
-static void d3d11va_uninit(struct lavc_ctx *s)
-{
- struct priv *p = s->hwdec_priv;
- if (!p)
- return;
-
- talloc_free(p->decoder);
- av_freep(&s->avctx->hwaccel_context);
-
- if (p->video_dev)
- ID3D11VideoDevice_Release(p->video_dev);
-
- if (p->video_ctx)
- ID3D11VideoContext_Release(p->video_ctx);
-
- destroy_device(s);
-
- TA_FREEP(&s->hwdec_priv);
-}
-
-static int d3d11va_init(struct lavc_ctx *s)
-{
- HRESULT hr;
- struct priv *p = talloc_zero(NULL, struct priv);
- if (!p)
- return -1;
-
- // Unconditionally load Direct3D DLLs, even when using a VO-supplied D3D11
- // device. This prevents a crash that occurs at least with NVIDIA drivers,
- // where D3D objects are accessed after ANGLE unloads d3d11.dll.
- d3d_load_dlls();
-
- s->hwdec_priv = p;
- p->log = mp_log_new(s, s->log, "d3d11va");
- if (s->hwdec->type == HWDEC_D3D11VA_COPY) {
- mp_check_gpu_memcpy(p->log, NULL);
- p->sw_pool = talloc_steal(p, mp_image_pool_new(17));
- }
-
- p->device = hwdec_devices_load(s->hwdec_devs, s->hwdec->type);
- if (p->device) {
- ID3D11Device_AddRef(p->device);
- ID3D11Device_GetImmediateContext(p->device, &p->device_ctx);
- if (!p->device_ctx)
- goto fail;
- MP_VERBOSE(p, "Using VO-supplied device %p.\n", p->device);
- } else if (s->hwdec->type == HWDEC_D3D11VA) {
- MP_ERR(p, "No Direct3D device provided for native d3d11 decoding\n");
- goto fail;
- } else {
- if (!create_device(s, FALSE))
- goto fail;
- }
-
- hr = ID3D11DeviceContext_QueryInterface(p->device_ctx,
- &IID_ID3D11VideoContext,
- (void **)&p->video_ctx);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get VideoContext interface: %s\n",
- mp_HRESULT_to_str(hr));
- goto fail;
- }
-
- hr = ID3D11Device_QueryInterface(p->device,
- &IID_ID3D11VideoDevice,
- (void **)&p->video_dev);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get VideoDevice interface. %s\n",
- mp_HRESULT_to_str(hr));
- goto fail;
- }
-
- s->avctx->hwaccel_context = av_d3d11va_alloc_context();
- if (!s->avctx->hwaccel_context) {
- MP_ERR(p, "Failed to allocate hwaccel_context\n");
- goto fail;
- }
-
- return 0;
-fail:
- d3d11va_uninit(s);
- return -1;
-}
-
-static int d3d11va_probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
- const char *codec)
-{
- // d3d11va-copy can do without external context; dxva2 requires it.
- if (hwdec->type != HWDEC_D3D11VA_COPY) {
- if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_D3D11VA))
- return HWDEC_ERR_NO_CTX;
- }
- return d3d_probe_codec(codec);
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_d3d11va = {
- .type = HWDEC_D3D11VA,
- .image_format = IMGFMT_D3D11VA,
- .probe = d3d11va_probe,
- .init = d3d11va_init,
- .uninit = d3d11va_uninit,
- .init_decoder = d3d11va_init_decoder,
- .allocate_image = d3d11va_allocate_image,
- .process_image = d3d11va_update_image_attribs,
-};
-
-const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy = {
- .type = HWDEC_D3D11VA_COPY,
- .copying = true,
- .image_format = IMGFMT_D3D11VA,
- .probe = d3d11va_probe,
- .init = d3d11va_init,
- .uninit = d3d11va_uninit,
- .init_decoder = d3d11va_init_decoder,
- .allocate_image = d3d11va_allocate_image,
- .process_image = d3d11va_retrieve_image,
- .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
-};
-
-#else /* !HAVE_D3D_HWACCEL_NEW */
-
-#include <libavutil/hwcontext.h>
-#include <libavutil/hwcontext_d3d11va.h>
-
-static void d3d11_destroy_dev(struct mp_hwdec_ctx *ctx)
-{
- av_buffer_unref(&ctx->av_device_ref);
- ID3D11Device_Release((ID3D11Device *)ctx->ctx);
- talloc_free(ctx);
-}
-
-static struct mp_hwdec_ctx *d3d11_create_dev(struct mpv_global *global,
- struct mp_log *plog, bool probing)
-{
- ID3D11Device *device = NULL;
- HRESULT hr;
-
- d3d_load_dlls();
- if (!d3d11_D3D11CreateDevice) {
- mp_err(plog, "Failed to load D3D11 library\n");
- return NULL;
- }
-
- hr = d3d11_D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
- D3D11_CREATE_DEVICE_VIDEO_SUPPORT, NULL, 0,
- D3D11_SDK_VERSION, &device, NULL, NULL);
- if (FAILED(hr)) {
- mp_err(plog, "Failed to create D3D11 Device: %s\n",
- mp_HRESULT_to_str(hr));
- return NULL;
- }
-
- struct mp_hwdec_ctx *ctx = talloc_ptrtype(NULL, ctx);
- *ctx = (struct mp_hwdec_ctx) {
- .type = HWDEC_D3D11VA_COPY,
- .ctx = device,
- .destroy = d3d11_destroy_dev,
- .av_device_ref = d3d11_wrap_device_ref(device),
- };
-
- if (!ctx->av_device_ref) {
- mp_err(plog, "Failed to allocate AVHWDeviceContext.\n");
- d3d11_destroy_dev(ctx);
- return NULL;
- }
-
- return ctx;
-}
-
-static struct mp_image *d3d11_update_image_attribs(struct lavc_ctx *s,
- struct mp_image *img)
-{
- if (img->params.hw_subfmt == IMGFMT_NV12)
- mp_image_setfmt(img, IMGFMT_D3D11NV12);
-
- return img;
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_d3d11va = {
- .type = HWDEC_D3D11VA,
- .image_format = IMGFMT_D3D11VA,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .static_pool = true,
- .hwframes_refine = d3d_hwframes_refine,
- .process_image = d3d11_update_image_attribs,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P10, AV_PIX_FMT_P010},
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_NONE}
- },
-};
-
-const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy = {
- .type = HWDEC_D3D11VA_COPY,
- .copying = true,
- .image_format = IMGFMT_D3D11VA,
- .generic_hwaccel = true,
- .create_dev = d3d11_create_dev,
- .set_hwframes = true,
- .static_pool = true,
- .hwframes_refine = d3d_hwframes_refine,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P10, AV_PIX_FMT_P010},
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_NONE}
- },
- .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
-};
-
-#endif /* else !HAVE_D3D_HWACCEL_NEW */
diff --git a/video/decode/hw_dxva2.c b/video/decode/hw_dxva2.c
deleted file mode 100644
index eef1ebf..0000000
--- a/video/decode/hw_dxva2.c
+++ /dev/null
@@ -1,724 +0,0 @@
-/*
- * Ported from FFmpeg ffmpeg_dxva2.c (2dbee1a3935a91842c22eb65fd13f77e8d590e07).
- * Original copyright header follows:
- *
- * 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
- */
-
-#define DXVA2API_USE_BITFIELDS
-#include <libavcodec/dxva2.h>
-#include <libavutil/common.h>
-
-#include "config.h"
-
-#include "lavc.h"
-#include "common/common.h"
-#include "common/av_common.h"
-#include "osdep/windows_utils.h"
-#include "video/fmt-conversion.h"
-#include "video/mp_image_pool.h"
-#include "video/hwdec.h"
-
-#include "d3d.h"
-
-#if !HAVE_D3D_HWACCEL_NEW
-
-#define ADDITIONAL_SURFACES HWDEC_EXTRA_SURFACES
-
-struct priv {
- struct mp_log *log;
-
- IDirect3D9 *d3d9;
- IDirect3DDevice9 *device;
- HANDLE device_handle;
- IDirect3DDeviceManager9 *device_manager;
- IDirectXVideoDecoderService *decoder_service;
-
- struct mp_image_pool *decoder_pool;
- struct mp_image_pool *sw_pool;
- int mpfmt_decoded;
-};
-
-struct dxva2_surface {
- IDirectXVideoDecoder *decoder;
- IDirect3DSurface9 *surface;
-};
-
-static void dxva2_release_img(void *arg)
-{
- struct dxva2_surface *surface = arg;
- if (surface->surface)
- IDirect3DSurface9_Release(surface->surface);
-
- if (surface->decoder)
- IDirectXVideoDecoder_Release(surface->decoder);
-
- talloc_free(surface);
-}
-
-static struct mp_image *dxva2_new_ref(IDirectXVideoDecoder *decoder,
- IDirect3DSurface9 *d3d9_surface,
- int w, int h)
-{
- if (!decoder || !d3d9_surface)
- return NULL;
- struct dxva2_surface *surface = talloc_zero(NULL, struct dxva2_surface);
-
- surface->surface = d3d9_surface;
- IDirect3DSurface9_AddRef(surface->surface);
- surface->decoder = decoder;
- IDirectXVideoDecoder_AddRef(surface->decoder);
-
- struct mp_image *mpi =
- mp_image_new_custom_ref(NULL, surface, dxva2_release_img);
- if (!mpi)
- abort();
-
- mp_image_setfmt(mpi, IMGFMT_DXVA2);
- mp_image_set_size(mpi, w, h);
- mpi->planes[3] = (void *)surface->surface;
- return mpi;
-}
-
-static struct mp_image *dxva2_allocate_image(struct lavc_ctx *s, int w, int h)
-{
- struct priv *p = s->hwdec_priv;
- struct mp_image *img = mp_image_pool_get_no_alloc(p->decoder_pool,
- IMGFMT_DXVA2, w, h);
- if (!img)
- MP_ERR(p, "Failed to allocate additional DXVA2 surface.\n");
- return img;
-}
-
-static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s,
- struct mp_image *img)
-{
- HRESULT hr;
- struct priv *p = s->hwdec_priv;
- IDirect3DSurface9 *surface = img->imgfmt == IMGFMT_DXVA2 ?
- (IDirect3DSurface9 *)img->planes[3] : NULL;
-
- if (!surface) {
- MP_ERR(p, "Failed to get Direct3D surface from mp_image\n");
- return img;
- }
-
- D3DSURFACE_DESC surface_desc;
- IDirect3DSurface9_GetDesc(surface, &surface_desc);
- if (surface_desc.Width < img->w || surface_desc.Height < img->h) {
- MP_ERR(p, "Direct3D11 texture smaller than mp_image dimensions\n");
- return img;
- }
-
- struct mp_image *sw_img = mp_image_pool_get(p->sw_pool,
- p->mpfmt_decoded,
- surface_desc.Width,
- surface_desc.Height);
- if (!sw_img) {
- MP_ERR(p, "Failed to get %s surface from CPU pool\n",
- mp_imgfmt_to_name(p->mpfmt_decoded));
- return img;
- }
-
- D3DLOCKED_RECT lock;
- hr = IDirect3DSurface9_LockRect(surface, &lock, NULL, D3DLOCK_READONLY);
- if (FAILED(hr)) {
- MP_ERR(p, "Unable to lock DXVA2 surface: %s\n",
- mp_HRESULT_to_str(hr));
- talloc_free(sw_img);
- return img;
- }
- copy_nv12(sw_img, lock.pBits, lock.Pitch, surface_desc.Height);
- IDirect3DSurface9_UnlockRect(surface);
-
- mp_image_set_size(sw_img, img->w, img->h);
- mp_image_copy_attributes(sw_img, img);
- talloc_free(img);
- return sw_img;
-}
-
-static const struct d3d_decoded_format d3d9_formats[] = {
- {MKTAG('N','V','1','2'), "NV12", 8, IMGFMT_NV12},
- {MKTAG('P','0','1','0'), "P010", 10, IMGFMT_P010},
- {MKTAG('P','0','1','6'), "P016", 16, IMGFMT_P010},
-};
-
-static void dump_decoder_info(struct lavc_ctx *s,
- GUID *device_guids, UINT n_guids)
-{
- struct priv *p = s->hwdec_priv;
- MP_VERBOSE(p, "%u decoder devices:\n", (unsigned)n_guids);
- for (UINT i = 0; i < n_guids; i++) {
- GUID *guid = &device_guids[i];
- char *description = d3d_decoder_guid_to_desc(guid);
-
- D3DFORMAT *formats = NULL;
- UINT n_formats = 0;
- HRESULT hr = IDirectXVideoDecoderService_GetDecoderRenderTargets(
- p->decoder_service, guid, &n_formats, &formats);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to get render targets for decoder %s:%s\n",
- description, mp_HRESULT_to_str(hr));
- }
-
- char fmts[256] = {0};
- for (UINT j = 0; j < n_formats; j++) {
- mp_snprintf_cat(fmts, sizeof(fmts),
- " %s", mp_tag_str(formats[j]));
- }
- CoTaskMemFree(formats);
-
- MP_VERBOSE(p, "%s %s\n", description, fmts);
- }
-}
-
-static bool dxva2_format_supported(struct lavc_ctx *s, const GUID *guid,
- const struct d3d_decoded_format *format)
-{
- bool ret = false;
- struct priv *p = s->hwdec_priv;
- D3DFORMAT *formats = NULL;
- UINT n_formats = 0;
- HRESULT hr = IDirectXVideoDecoderService_GetDecoderRenderTargets(
- p->decoder_service, guid, &n_formats, &formats);
- if (FAILED(hr)) {
- MP_ERR(p, "Callback failed to get render targets for decoder %s: %s",
- d3d_decoder_guid_to_desc(guid), mp_HRESULT_to_str(hr));
- return 0;
- }
-
- for (int i = 0; i < n_formats; i++) {
- ret = formats[i] == format->dxfmt;
- if (ret)
- break;
- }
-
- CoTaskMemFree(formats);
- return ret;
-}
-
-static int dxva2_init_decoder(struct lavc_ctx *s, int w, int h)
-{
- HRESULT hr;
- int ret = -1;
- struct priv *p = s->hwdec_priv;
- TA_FREEP(&p->decoder_pool);
-
- int n_surfaces = hwdec_get_max_refs(s) + ADDITIONAL_SURFACES;
- IDirect3DSurface9 **surfaces = NULL;
- IDirectXVideoDecoder *decoder = NULL;
- void *tmp = talloc_new(NULL);
-
- UINT n_guids;
- GUID *device_guids;
- hr = IDirectXVideoDecoderService_GetDecoderDeviceGuids(
- p->decoder_service, &n_guids, &device_guids);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to retrieve decoder device GUIDs: %s\n",
- mp_HRESULT_to_str(hr));
- goto done;
- }
-
- dump_decoder_info(s, device_guids, n_guids);
-
- struct d3d_decoder_fmt fmt =
- d3d_select_decoder_mode(s, device_guids, n_guids,
- d3d9_formats, MP_ARRAY_SIZE(d3d9_formats),
- dxva2_format_supported);
- CoTaskMemFree(device_guids);
- if (!fmt.format) {
- MP_ERR(p, "Failed to find a suitable decoder\n");
- goto done;
- }
-
- p->mpfmt_decoded = fmt.format->mpfmt;
- struct mp_image_pool *decoder_pool =
- talloc_steal(tmp, mp_image_pool_new(n_surfaces));
- DXVA2_ConfigPictureDecode *decoder_config =
- talloc_zero(decoder_pool, DXVA2_ConfigPictureDecode);
-
- int w_align = w, h_align = h;
- d3d_surface_align(s, &w_align, &h_align);
- DXVA2_VideoDesc video_desc ={
- .SampleWidth = w,
- .SampleHeight = h,
- .Format = fmt.format->dxfmt,
- };
- UINT n_configs = 0;
- DXVA2_ConfigPictureDecode *configs = NULL;
- hr = IDirectXVideoDecoderService_GetDecoderConfigurations(
- p->decoder_service, fmt.guid, &video_desc, NULL,
- &n_configs, &configs);
- if (FAILED(hr)) {
- MP_ERR(p, "Unable to retrieve decoder configurations: %s\n",
- mp_HRESULT_to_str(hr));
- goto done;
- }
-
- unsigned max_score = 0;
- for (UINT i = 0; i < n_configs; i++) {
- unsigned score = d3d_decoder_config_score(
- s, &configs[i].guidConfigBitstreamEncryption,
- configs[i].ConfigBitstreamRaw);
- if (score > max_score) {
- max_score = score;
- *decoder_config = configs[i];
- }
- }
- CoTaskMemFree(configs);
- if (!max_score) {
- MP_ERR(p, "Failed to find a suitable decoder configuration\n");
- goto done;
- }
-
- surfaces = talloc_zero_array(decoder_pool, IDirect3DSurface9*, n_surfaces);
- hr = IDirectXVideoDecoderService_CreateSurface(
- p->decoder_service,
- w_align, h_align,
- n_surfaces - 1, fmt.format->dxfmt, D3DPOOL_DEFAULT, 0,
- DXVA2_VideoDecoderRenderTarget, surfaces, NULL);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create %d video surfaces: %s\n",
- n_surfaces, mp_HRESULT_to_str(hr));
- goto done;
- }
-
- hr = IDirectXVideoDecoderService_CreateVideoDecoder(
- p->decoder_service, fmt.guid, &video_desc, decoder_config,
- surfaces, n_surfaces, &decoder);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create DXVA2 video decoder: %s\n",
- mp_HRESULT_to_str(hr));
- goto done;
- }
-
- for (int i = 0; i < n_surfaces; i++) {
- struct mp_image *img = dxva2_new_ref(decoder, surfaces[i], w, h);
- if (!img) {
- MP_ERR(p, "Failed to create DXVA2 image\n");
- goto done;
- }
- mp_image_pool_add(decoder_pool, img); // transferred to pool
- }
-
- // Pass required information on to ffmpeg.
- struct dxva_context *dxva_ctx = s->avctx->hwaccel_context;
- dxva_ctx->cfg = decoder_config;
- dxva_ctx->decoder = decoder;
- dxva_ctx->surface_count = n_surfaces;
- dxva_ctx->surface = surfaces;
- dxva_ctx->workaround = is_clearvideo(fmt.guid) ?
- FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO : 0;
-
- p->decoder_pool = talloc_steal(NULL, decoder_pool);
- ret = 0;
-done:
- // On success, `p->decoder_pool` mp_images still hold refs to `surfaces` and
- // `decoder`, so the pointers in the ffmpeg `dxva_context` strcture remain
- // valid for the lifetime of the pool.
- if (surfaces) {
- for (int i = 0; i < n_surfaces; i++)
- IDirect3DSurface9_Release(surfaces[i]);
- }
- if (decoder)
- IDirectXVideoDecoder_Release(decoder);
-
- talloc_free(tmp);
- return ret;
-}
-
-static void destroy_device(struct lavc_ctx *s)
-{
- struct priv *p = s->hwdec_priv;
-
- if (p->device)
- IDirect3DDevice9_Release(p->device);
-
- if (p->d3d9)
- IDirect3D9_Release(p->d3d9);
-}
-
-static bool create_device(struct lavc_ctx *s)
-{
- struct priv *p = s->hwdec_priv;
-
- d3d_load_dlls();
- if (!d3d9_dll) {
- MP_ERR(p, "Failed to load D3D9 library\n");
- return false;
- }
-
- HRESULT (WINAPI *Direct3DCreate9Ex)(UINT, IDirect3D9Ex **) =
- (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
- if (!Direct3DCreate9Ex) {
- MP_ERR(p, "Failed to locate Direct3DCreate9Ex\n");
- return false;
- }
-
- IDirect3D9Ex *d3d9ex = NULL;
- HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create IDirect3D9Ex object\n");
- return false;
- }
-
- UINT adapter = D3DADAPTER_DEFAULT;
- D3DDISPLAYMODEEX modeex = {0};
- IDirect3D9Ex_GetAdapterDisplayModeEx(d3d9ex, adapter, &modeex, NULL);
-
- D3DPRESENT_PARAMETERS present_params = {
- .Windowed = TRUE,
- .BackBufferWidth = 640,
- .BackBufferHeight = 480,
- .BackBufferCount = 0,
- .BackBufferFormat = modeex.Format,
- .SwapEffect = D3DSWAPEFFECT_DISCARD,
- .Flags = D3DPRESENTFLAG_VIDEO,
- };
-
- IDirect3DDevice9Ex *exdev = NULL;
- hr = IDirect3D9Ex_CreateDeviceEx(d3d9ex, adapter,
- D3DDEVTYPE_HAL,
- GetShellWindow(),
- D3DCREATE_SOFTWARE_VERTEXPROCESSING |
- D3DCREATE_MULTITHREADED |
- D3DCREATE_FPU_PRESERVE,
- &present_params,
- NULL,
- &exdev);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create Direct3D device: %s\n",
- mp_HRESULT_to_str(hr));
- IDirect3D9_Release(d3d9ex);
- return false;
- }
-
- p->d3d9 = (IDirect3D9 *)d3d9ex;
- p->device = (IDirect3DDevice9 *)exdev;
- return true;
-}
-
-static void dxva2_uninit(struct lavc_ctx *s)
-{
- struct priv *p = s->hwdec_priv;
- if (!p)
- return;
-
- av_freep(&s->avctx->hwaccel_context);
- talloc_free(p->decoder_pool);
-
- if (p->decoder_service)
- IDirectXVideoDecoderService_Release(p->decoder_service);
-
- if (p->device_manager && p->device_handle != INVALID_HANDLE_VALUE)
- IDirect3DDeviceManager9_CloseDeviceHandle(p->device_manager, p->device_handle);
-
- if (p->device_manager)
- IDirect3DDeviceManager9_Release(p->device_manager);
-
- destroy_device(s);
-
- TA_FREEP(&s->hwdec_priv);
-}
-
-static int dxva2_init(struct lavc_ctx *s)
-{
- HRESULT hr;
- struct priv *p = talloc_zero(NULL, struct priv);
- if (!p)
- return -1;
-
- s->hwdec_priv = p;
- p->device_handle = INVALID_HANDLE_VALUE;
- p->log = mp_log_new(s, s->log, "dxva2");
-
- if (s->hwdec->type == HWDEC_DXVA2_COPY) {
- mp_check_gpu_memcpy(p->log, NULL);
- p->sw_pool = talloc_steal(p, mp_image_pool_new(17));
- }
-
- p->device = hwdec_devices_load(s->hwdec_devs, s->hwdec->type);
- if (p->device) {
- IDirect3D9_AddRef(p->device);
- MP_VERBOSE(p, "Using VO-supplied device %p.\n", p->device);
- } else if (s->hwdec->type == HWDEC_DXVA2) {
- MP_ERR(p, "No Direct3D device provided for native dxva2 decoding\n");
- goto fail;
- } else {
- if (!create_device(s))
- goto fail;
- }
-
- d3d_load_dlls();
- if (!dxva2_dll) {
- MP_ERR(p, "Failed to load DXVA2 library\n");
- goto fail;
- }
-
- HRESULT (WINAPI *CreateDeviceManager9)(UINT *, IDirect3DDeviceManager9 **) =
- (void *)GetProcAddress(dxva2_dll, "DXVA2CreateDirect3DDeviceManager9");
- if (!CreateDeviceManager9) {
- MP_ERR(p, "Failed to locate DXVA2CreateDirect3DDeviceManager9\n");
- goto fail;
- }
-
- unsigned reset_token = 0;
- hr = CreateDeviceManager9(&reset_token, &p->device_manager);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create Direct3D device manager: %s\n",
- mp_HRESULT_to_str(hr));
- goto fail;
- }
-
- hr = IDirect3DDeviceManager9_ResetDevice(p->device_manager,
- p->device, reset_token);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to bind Direct3D device to device manager: %s\n",
- mp_HRESULT_to_str(hr));
- goto fail;
- }
-
- hr = IDirect3DDeviceManager9_OpenDeviceHandle(p->device_manager,
- &p->device_handle);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to open device handle: %s\n",
- mp_HRESULT_to_str(hr));
- goto fail;
- }
-
- hr = IDirect3DDeviceManager9_GetVideoService(
- p->device_manager, p->device_handle, &IID_IDirectXVideoDecoderService,
- (void **)&p->decoder_service);
- if (FAILED(hr)) {
- MP_ERR(p, "Failed to create IDirectXVideoDecoderService: %s\n",
- mp_HRESULT_to_str(hr));
- goto fail;
- }
-
- s->avctx->hwaccel_context = av_mallocz(sizeof(struct dxva_context));
- if (!s->avctx->hwaccel_context)
- goto fail;
-
- return 0;
-fail:
- dxva2_uninit(s);
- return -1;
-}
-
-static int dxva2_probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
- const char *codec)
-{
- // dxva2-copy can do without external context; dxva2 requires it.
- if (hwdec->type == HWDEC_DXVA2) {
- if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_DXVA2))
- return HWDEC_ERR_NO_CTX;
- } else {
- hwdec_devices_load(ctx->hwdec_devs, HWDEC_DXVA2_COPY);
- }
- return d3d_probe_codec(codec);
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_dxva2 = {
- .type = HWDEC_DXVA2,
- .image_format = IMGFMT_DXVA2,
- .probe = dxva2_probe,
- .init = dxva2_init,
- .uninit = dxva2_uninit,
- .init_decoder = dxva2_init_decoder,
- .allocate_image = dxva2_allocate_image,
-};
-
-const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy = {
- .type = HWDEC_DXVA2_COPY,
- .copying = true,
- .image_format = IMGFMT_DXVA2,
- .probe = dxva2_probe,
- .init = dxva2_init,
- .uninit = dxva2_uninit,
- .init_decoder = dxva2_init_decoder,
- .allocate_image = dxva2_allocate_image,
- .process_image = dxva2_retrieve_image,
- .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
-};
-
-#else /* !HAVE_D3D_HWACCEL_NEW */
-
-#include <libavutil/hwcontext.h>
-#include <libavutil/hwcontext_dxva2.h>
-
-static void d3d9_free_av_device_ref(AVHWDeviceContext *ctx)
-{
- AVDXVA2DeviceContext *hwctx = ctx->hwctx;
-
- if (hwctx->devmgr)
- IDirect3DDeviceManager9_Release(hwctx->devmgr);
-}
-
-AVBufferRef *d3d9_wrap_device_ref(IDirect3DDevice9 *device)
-{
- HRESULT hr;
-
- d3d_load_dlls();
- if (!dxva2_dll)
- return NULL;
-
- HRESULT (WINAPI *DXVA2CreateDirect3DDeviceManager9)(UINT *, IDirect3DDeviceManager9 **) =
- (void *)GetProcAddress(dxva2_dll, "DXVA2CreateDirect3DDeviceManager9");
- if (!DXVA2CreateDirect3DDeviceManager9)
- return NULL;
-
- AVBufferRef *device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DXVA2);
- if (!device_ref)
- return NULL;
-
- AVHWDeviceContext *ctx = (void *)device_ref->data;
- AVDXVA2DeviceContext *hwctx = ctx->hwctx;
-
- UINT reset_token = 0;
- hr = DXVA2CreateDirect3DDeviceManager9(&reset_token, &hwctx->devmgr);
- if (FAILED(hr))
- goto fail;
-
- IDirect3DDeviceManager9_ResetDevice(hwctx->devmgr, device, reset_token);
- if (FAILED(hr))
- goto fail;
-
- ctx->free = d3d9_free_av_device_ref;
-
- if (av_hwdevice_ctx_init(device_ref) < 0)
- goto fail;
-
- return device_ref;
-
-fail:
- d3d9_free_av_device_ref(ctx);
- av_buffer_unref(&device_ref);
- return NULL;
-}
-
-static void d3d9_destroy_dev(struct mp_hwdec_ctx *ctx)
-{
- av_buffer_unref(&ctx->av_device_ref);
- IDirect3DDevice9_Release((IDirect3DDevice9 *)ctx->ctx);
- talloc_free(ctx);
-}
-
-static struct mp_hwdec_ctx *d3d9_create_dev(struct mpv_global *global,
- struct mp_log *plog, bool probing)
-{
- d3d_load_dlls();
- if (!d3d9_dll || !dxva2_dll) {
- mp_err(plog, "Failed to load D3D9 library\n");
- return NULL;
- }
-
- HRESULT (WINAPI *Direct3DCreate9Ex)(UINT, IDirect3D9Ex **) =
- (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
- if (!Direct3DCreate9Ex) {
- mp_err(plog, "Failed to locate Direct3DCreate9Ex\n");
- return NULL;
- }
-
- IDirect3D9Ex *d3d9ex = NULL;
- HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
- if (FAILED(hr)) {
- mp_err(plog, "Failed to create IDirect3D9Ex object\n");
- return NULL;
- }
-
- UINT adapter = D3DADAPTER_DEFAULT;
- D3DDISPLAYMODEEX modeex = {0};
- IDirect3D9Ex_GetAdapterDisplayModeEx(d3d9ex, adapter, &modeex, NULL);
-
- D3DPRESENT_PARAMETERS present_params = {
- .Windowed = TRUE,
- .BackBufferWidth = 640,
- .BackBufferHeight = 480,
- .BackBufferCount = 0,
- .BackBufferFormat = modeex.Format,
- .SwapEffect = D3DSWAPEFFECT_DISCARD,
- .Flags = D3DPRESENTFLAG_VIDEO,
- };
-
- IDirect3DDevice9Ex *exdev = NULL;
- hr = IDirect3D9Ex_CreateDeviceEx(d3d9ex, adapter,
- D3DDEVTYPE_HAL,
- GetShellWindow(),
- D3DCREATE_SOFTWARE_VERTEXPROCESSING |
- D3DCREATE_MULTITHREADED |
- D3DCREATE_FPU_PRESERVE,
- &present_params,
- NULL,
- &exdev);
- IDirect3D9_Release(d3d9ex);
- if (FAILED(hr)) {
- mp_err(plog, "Failed to create Direct3D device: %s\n",
- mp_HRESULT_to_str(hr));
- return NULL;
- }
-
- struct mp_hwdec_ctx *ctx = talloc_ptrtype(NULL, ctx);
- *ctx = (struct mp_hwdec_ctx) {
- .type = HWDEC_D3D11VA_COPY,
- .ctx = exdev,
- .destroy = d3d9_destroy_dev,
- .av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)exdev),
- };
-
- if (!ctx->av_device_ref) {
- mp_err(plog, "Failed to allocate AVHWDeviceContext.\n");
- d3d9_destroy_dev(ctx);
- return NULL;
- }
-
- return ctx;
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_dxva2 = {
- .type = HWDEC_DXVA2,
- .image_format = IMGFMT_DXVA2,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .static_pool = true,
- .hwframes_refine = d3d_hwframes_refine,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P10, AV_PIX_FMT_P010},
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_NONE}
- },
-};
-
-const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy = {
- .type = HWDEC_DXVA2_COPY,
- .copying = true,
- .image_format = IMGFMT_DXVA2,
- .generic_hwaccel = true,
- .create_dev = d3d9_create_dev,
- .set_hwframes = true,
- .static_pool = true,
- .hwframes_refine = d3d_hwframes_refine,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P10, AV_PIX_FMT_P010},
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_NONE}
- },
- .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
-};
-
-#endif /* else #if !HAVE_D3D_HWACCEL_NEW */
diff --git a/video/decode/hw_videotoolbox.c b/video/decode/hw_videotoolbox.c
deleted file mode 100644
index d731335..0000000
--- a/video/decode/hw_videotoolbox.c
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * Copyright (c) 2015 Sebastien Zwickert
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "config.h"
-
-#if !HAVE_VIDEOTOOLBOX_HWACCEL_NEW
-
-#include <libavcodec/version.h>
-#include <libavcodec/videotoolbox.h>
-
-#include "common/av_common.h"
-#include "common/msg.h"
-#include "options/options.h"
-#include "video/mp_image.h"
-#include "video/decode/lavc.h"
-#include "video/mp_image_pool.h"
-#include "video/vt.h"
-#include "config.h"
-
-struct priv {
- struct mp_image_pool *sw_pool;
-};
-
-static int probe_copy(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
- const char *codec)
-{
- switch (mp_codec_to_av_codec_id(codec)) {
- case AV_CODEC_ID_H264:
- case AV_CODEC_ID_H263:
- case AV_CODEC_ID_MPEG1VIDEO:
- case AV_CODEC_ID_MPEG2VIDEO:
- case AV_CODEC_ID_MPEG4:
- break;
- default:
- return HWDEC_ERR_NO_CODEC;
- }
- return 0;
-}
-
-static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
- const char *codec)
-{
- if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_VIDEOTOOLBOX))
- return HWDEC_ERR_NO_CTX;
- return probe_copy(ctx, hwdec, codec);
-}
-
-static int init(struct lavc_ctx *ctx)
-{
- struct priv *p = talloc_ptrtype(NULL, p);
- p->sw_pool = talloc_steal(p, mp_image_pool_new(17));
- ctx->hwdec_priv = p;
- return 0;
-}
-
-struct videotoolbox_error {
- int code;
- char *reason;
-};
-
-static const struct videotoolbox_error videotoolbox_errors[] = {
- { AVERROR(ENOSYS),
- "Hardware doesn't support accelerated decoding for this stream"
- " or Videotoolbox decoder is not available at the moment (another"
- " application is using it)."
- },
- { AVERROR(EINVAL),
- "Invalid configuration provided to VTDecompressionSessionCreate" },
- { AVERROR_INVALIDDATA,
- "Generic error returned by the decoder layer. The cause can be Videotoolbox"
- " found errors in the bitstream." },
- { 0, NULL },
-};
-
-static void print_videotoolbox_error(struct mp_log *log, int lev, char *message,
- int error_code)
-{
- for (int n = 0; videotoolbox_errors[n].code < 0; n++)
- if (videotoolbox_errors[n].code == error_code) {
- mp_msg(log, lev, "%s: %s (%d)\n",
- message, videotoolbox_errors[n].reason, error_code);
- return;
- }
-
- mp_msg(log, lev, "%s: %d\n", message, error_code);
-}
-
-static int init_decoder(struct lavc_ctx *ctx, int w, int h)
-{
- av_videotoolbox_default_free(ctx->avctx);
-
- AVVideotoolboxContext *vtctx = av_videotoolbox_alloc_context();
- if (!vtctx)
- return -1;
-
- int imgfmt = ctx->opts->videotoolbox_format;
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 81, 103)
- if (!imgfmt)
- imgfmt = IMGFMT_NV12;
-#endif
- vtctx->cv_pix_fmt_type = mp_imgfmt_to_cvpixelformat(imgfmt);
- MP_VERBOSE(ctx, "Requesting cv_pix_fmt_type=0x%x\n",
- (unsigned)vtctx->cv_pix_fmt_type);
-
- int err = av_videotoolbox_default_init2(ctx->avctx, vtctx);
- if (err < 0) {
- print_videotoolbox_error(ctx->log, MSGL_ERR, "failed to init videotoolbox decoder", err);
- return -1;
- }
-
- return 0;
-}
-
-static void uninit(struct lavc_ctx *ctx)
-{
- if (ctx->avctx)
- av_videotoolbox_default_free(ctx->avctx);
-
- struct priv *p = ctx->hwdec_priv;
- if (!p)
- return;
-
- talloc_free(p->sw_pool);
- p->sw_pool = NULL;
-
- talloc_free(p);
- ctx->hwdec_priv = NULL;
-}
-
-static struct mp_image *copy_image(struct lavc_ctx *ctx, struct mp_image *hw_image)
-{
- struct priv *p = ctx->hwdec_priv;
-
- struct mp_image *image = mp_vt_download_image(NULL, hw_image, p->sw_pool);
- if (image) {
- talloc_free(hw_image);
- return image;
- } else {
- return hw_image;
- }
-}
-
-static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *img)
-{
- if (img->imgfmt == IMGFMT_VIDEOTOOLBOX) {
- CVPixelBufferRef pbuf = (CVPixelBufferRef)img->planes[3];
- uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(pbuf);
- img->params.hw_subfmt = mp_imgfmt_from_cvpixelformat(cvpixfmt);
- }
- return img;
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = {
- .type = HWDEC_VIDEOTOOLBOX,
- .image_format = IMGFMT_VIDEOTOOLBOX,
- .probe = probe,
- .init = init,
- .uninit = uninit,
- .init_decoder = init_decoder,
- .process_image = process_image,
-};
-
-const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox_copy = {
- .type = HWDEC_VIDEOTOOLBOX_COPY,
- .copying = true,
- .image_format = IMGFMT_VIDEOTOOLBOX,
- .probe = probe_copy,
- .init = init,
- .uninit = uninit,
- .init_decoder = init_decoder,
- .process_image = copy_image,
- .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
-};
-
-#else
-
-#include <libavutil/hwcontext.h>
-
-#include "video/decode/lavc.h"
-
-static void vt_dummy_destroy(struct mp_hwdec_ctx *ctx)
-{
- av_buffer_unref(&ctx->av_device_ref);
- talloc_free(ctx);
-}
-
-static struct mp_hwdec_ctx *vt_create_dummy(struct mpv_global *global,
- struct mp_log *plog, bool probing)
-{
- struct mp_hwdec_ctx *ctx = talloc_ptrtype(NULL, ctx);
- *ctx = (struct mp_hwdec_ctx) {
- .type = HWDEC_VIDEOTOOLBOX_COPY,
- .ctx = "dummy",
- .destroy = vt_dummy_destroy,
- };
-
- if (av_hwdevice_ctx_create(&ctx->av_device_ref, AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
- NULL, NULL, 0) < 0)
- {
- vt_dummy_destroy(ctx);
- return NULL;
- }
-
- return ctx;
-}
-
-const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = {
- .type = HWDEC_VIDEOTOOLBOX,
- .image_format = IMGFMT_VIDEOTOOLBOX,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_NONE}
- },
-};
-
-const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox_copy = {
- .type = HWDEC_VIDEOTOOLBOX_COPY,
- .copying = true,
- .image_format = IMGFMT_VIDEOTOOLBOX,
- .generic_hwaccel = true,
- .create_dev = vt_create_dummy,
- .set_hwframes = true,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_NONE}
- },
- .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
-};
-
-#endif
diff --git a/video/decode/lavc.h b/video/decode/lavc.h
deleted file mode 100644
index 08d4dc1..0000000
--- a/video/decode/lavc.h
+++ /dev/null
@@ -1,151 +0,0 @@
-#ifndef MPV_LAVC_H
-#define MPV_LAVC_H
-
-#include <stdbool.h>
-#include <pthread.h>
-
-#include <libavcodec/avcodec.h>
-
-#include "config.h"
-
-#include "demux/stheader.h"
-#include "video/mp_image.h"
-#include "video/mp_image_pool.h"
-#include "video/hwdec.h"
-
-#define HWDEC_DELAY_QUEUE_COUNT 2
-
-// Maximum number of surfaces the player wants to buffer.
-// This number might require adjustment depending on whatever the player does;
-// for example, if vo_opengl increases the number of reference surfaces for
-// interpolation, this value has to be increased too.
-#define HWDEC_EXTRA_SURFACES 6
-
-struct mpv_global;
-
-typedef struct lavc_ctx {
- struct mp_log *log;
- struct MPOpts *opts;
- AVCodecContext *avctx;
- AVFrame *pic;
- struct vd_lavc_hwdec *hwdec;
- AVRational codec_timebase;
- enum AVPixelFormat pix_fmt;
- enum AVDiscard skip_frame;
- bool flushing;
- const char *decoder;
- bool hwdec_failed;
- bool hwdec_notified;
-
- int framedrop_flags;
-
- // For HDR side-data caching
- float cached_sig_peak;
-
- bool hw_probing;
- struct demux_packet **sent_packets;
- int num_sent_packets;
-
- struct demux_packet **requeue_packets;
- int num_requeue_packets;
-
- struct mp_image **delay_queue;
- int num_delay_queue;
- int max_delay_queue;
-
- // From VO
- struct mp_hwdec_devices *hwdec_devs;
-
- // For free use by hwdec implementation
- void *hwdec_priv;
-
- // Set by generic hwaccels.
- struct mp_hwdec_ctx *hwdec_dev;
- bool owns_hwdec_dev;
-
- int hwdec_fmt;
- int hwdec_w;
- int hwdec_h;
- int hwdec_profile;
-
- bool hwdec_request_reinit;
- int hwdec_fail_count;
-
- struct mp_image_pool *hwdec_swpool;
-
- AVBufferRef *cached_hw_frames_ctx;
-
- // --- The following fields are protected by dr_lock.
- pthread_mutex_t dr_lock;
- bool dr_failed;
- struct mp_image_pool *dr_pool;
- int dr_imgfmt, dr_w, dr_h, dr_stride_align;
-} vd_ffmpeg_ctx;
-
-struct vd_lavc_hwdec {
- enum hwdec_type type;
- // If not-0: the IMGFMT_ format that should be accepted in the libavcodec
- // get_format callback.
- int image_format;
- // Always returns a non-hwaccel image format.
- bool copying;
- // Setting this will queue the given number of frames before calling
- // process_image() or returning them to the renderer. This can increase
- // efficiency by not blocking on the hardware pipeline by reading back
- // immediately after decoding.
- int delay_queue;
- // If true, AVCodecContext will destroy the underlying decoder.
- bool volatile_context;
- int (*probe)(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
- const char *codec);
- int (*init)(struct lavc_ctx *ctx);
- int (*init_decoder)(struct lavc_ctx *ctx, int w, int h);
- void (*uninit)(struct lavc_ctx *ctx);
- // Note: if init_decoder is set, this will always use the values from the
- // last successful init_decoder call. Otherwise, it's up to you.
- struct mp_image *(*allocate_image)(struct lavc_ctx *ctx, int w, int h);
- // Process the image returned by the libavcodec decoder.
- struct mp_image *(*process_image)(struct lavc_ctx *ctx, struct mp_image *img);
- // For copy hwdecs. If probing is true, don't log errors if unavailable.
- // The returned device will be freed with mp_hwdec_ctx->destroy.
- struct mp_hwdec_ctx *(*create_dev)(struct mpv_global *global,
- struct mp_log *log, bool probing);
- // Optional. Fill in special hwaccel- and codec-specific requirements.
- void (*hwframes_refine)(struct lavc_ctx *ctx, AVBufferRef *hw_frames_ctx);
- // Suffix for libavcodec decoder. If non-NULL, the codec is overridden
- // with hwdec_find_decoder.
- // Intuitively, this will force the corresponding wrapper decoder.
- const char *lavc_suffix;
- // Generic hwaccels set AVCodecContext.hw_frames_ctx in get_format().
- bool generic_hwaccel;
- // If set, AVCodecContext.hw_frames_ctx will be initialized in get_format,
- // and pixfmt_map must be non-NULL.
- bool set_hwframes;
- // Array of pixfmt pairs. The first pixfmt is the AVCodecContext.sw_pix_fmt,
- // the second the required AVHWFramesContext.sw_format.
- const enum AVPixelFormat (*pixfmt_map)[2];
- // The generic hwaccel has a fixed pool size. Enough surfaces need to be
- // preallocated before decoding begins. If false, pool size is left to 0.
- bool static_pool;
-};
-
-enum {
- HWDEC_ERR_NO_CTX = -2,
- HWDEC_ERR_NO_CODEC = -3,
- HWDEC_ERR_EMULATED = -4, // probing successful, but emulated API detected
-};
-
-struct hwdec_profile_entry {
- enum AVCodecID av_codec;
- int ff_profile;
- uint64_t hw_profile;
-};
-
-int hwdec_get_max_refs(struct lavc_ctx *ctx);
-int hwdec_setup_hw_frames_ctx(struct lavc_ctx *ctx, AVBufferRef *device_ctx,
- int av_sw_format, int initial_pool_size);
-
-#define NEW_CUDA_HWACCEL \
- (HAVE_CUDA_HWACCEL && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 94, 100))
-
-#endif
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index 476beeb..6d92702 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -1,40 +1,30 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPLv3+.
- *
- * The parts potentially making this file LGPL v3 (instead of v2.1 or later) are:
- * 376e3abf5c7d2 xvmc use get_format for IDCT/MC recognition
- * c73f0e18bd1d6 Return PIX_FMT_NONE if the video system refuses all other formats.
- * (iive agreed to LGPL v3+ only. Jeremy agreed to LGPL v2.1 or later.)
- * Once these changes are not relevant to for copyright anymore (e.g. because
- * they have been removed), and the core is LGPL, this file will change to
- * LGPLv2.1+.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
+#include <pthread.h>
#include <assert.h>
-#include <time.h>
#include <stdbool.h>
-#include <sys/types.h>
+#include <libavcodec/avcodec.h>
#include <libavutil/common.h>
-#include <libavutil/opt.h>
#include <libavutil/hwcontext.h>
+#include <libavutil/opt.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/pixdesc.h>
@@ -49,8 +39,11 @@
#include "video/fmt-conversion.h"
#include "vd.h"
+#include "video/hwdec.h"
#include "video/img_format.h"
#include "video/filter/vf.h"
+#include "video/mp_image.h"
+#include "video/mp_image_pool.h"
#include "video/decode/dec_video.h"
#include "demux/demux.h"
#include "demux/stheader.h"
@@ -59,27 +52,23 @@
#include "video/sws_utils.h"
#include "video/out/vo.h"
-#if LIBAVCODEC_VERSION_MICRO >= 100
-#include <libavutil/mastering_display_metadata.h>
-#endif
-
-#include "lavc.h"
-
-#if AVPALETTE_SIZE != MP_PALETTE_SIZE
-#error palette too large, adapt video/mp_image.h:MP_PALETTE_SIZE
-#endif
-
#include "options/m_option.h"
-static void init_avctx(struct dec_video *vd, const char *decoder,
- struct vd_lavc_hwdec *hwdec);
+static void init_avctx(struct dec_video *vd);
static void uninit_avctx(struct dec_video *vd);
static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags);
-static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags);
static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
const enum AVPixelFormat *pix_fmt);
+#define HWDEC_DELAY_QUEUE_COUNT 2
+
+// Maximum number of surfaces the player wants to buffer.
+// This number might require adjustment depending on whatever the player does;
+// for example, if vo_opengl increases the number of reference surfaces for
+// interpolation, this value has to be increased too.
+#define HWDEC_EXTRA_SURFACES 6
+
#define OPT_BASE_STRUCT struct vd_lavc_params
struct vd_lavc_params {
@@ -139,171 +128,230 @@ const struct m_sub_options vd_lavc_conf = {
},
};
-extern const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox;
-extern const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox_copy;
-extern const struct vd_lavc_hwdec mp_vd_lavc_dxva2;
-extern const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy;
-extern const struct vd_lavc_hwdec mp_vd_lavc_d3d11va;
-extern const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy;
-extern const struct vd_lavc_hwdec mp_vd_lavc_cuda_old;
-
-#if HAVE_RPI
-static const struct vd_lavc_hwdec mp_vd_lavc_rpi = {
- .type = HWDEC_RPI,
- .lavc_suffix = "_mmal",
- .image_format = IMGFMT_MMAL,
+struct hwdec_info {
+ char name[64];
+ char method_name[16]; // non-unique name describing the hwdec method
+ const AVCodec *codec; // implemented by this codec
+ enum AVHWDeviceType lavc_device; // if not NONE, get a hwdevice
+ bool copying; // if true, outputs sw frames, or copy to sw ourselves
+ enum AVPixelFormat pix_fmt; // if not NONE, select in get_format
+ bool use_hw_frames; // set AVCodecContext.hw_frames_ctx
+ bool use_hw_device; // set AVCodecContext.hw_device_ctx
+
+ // for internal sorting
+ int auto_pos;
+ int rank;
};
-static const struct vd_lavc_hwdec mp_vd_lavc_rpi_copy = {
- .type = HWDEC_RPI_COPY,
- .lavc_suffix = "_mmal",
- .copying = true,
-};
-#endif
-#if HAVE_ANDROID
-static const struct vd_lavc_hwdec mp_vd_lavc_mediacodec = {
- .type = HWDEC_MEDIACODEC,
- .lavc_suffix = "_mediacodec",
- .copying = true,
+typedef struct lavc_ctx {
+ struct mp_log *log;
+ struct MPOpts *opts;
+ AVCodecContext *avctx;
+ AVFrame *pic;
+ bool use_hwdec;
+ struct hwdec_info hwdec; // valid only if use_hwdec==true
+ AVRational codec_timebase;
+ enum AVDiscard skip_frame;
+ bool flushing;
+ const char *decoder;
+ bool hwdec_requested;
+ bool hwdec_failed;
+ bool hwdec_notified;
+
+ bool intra_only;
+ int framedrop_flags;
+
+ bool hw_probing;
+ struct demux_packet **sent_packets;
+ int num_sent_packets;
+
+ struct demux_packet **requeue_packets;
+ int num_requeue_packets;
+
+ struct mp_image **delay_queue;
+ int num_delay_queue;
+ int max_delay_queue;
+
+ // From VO
+ struct mp_hwdec_devices *hwdec_devs;
+
+ // Wrapped AVHWDeviceContext* used for decoding.
+ AVBufferRef *hwdec_dev;
+
+ bool hwdec_request_reinit;
+ int hwdec_fail_count;
+
+ struct mp_image_pool *hwdec_swpool;
+
+ AVBufferRef *cached_hw_frames_ctx;
+
+ // --- The following fields are protected by dr_lock.
+ pthread_mutex_t dr_lock;
+ bool dr_failed;
+ struct mp_image_pool *dr_pool;
+ int dr_imgfmt, dr_w, dr_h, dr_stride_align;
+} vd_ffmpeg_ctx;
+
+// Things not included in this list will be tried last, in random order.
+static const char *const hwdec_autoprobe_order[] = {
+ "d3d11va",
+ "dxva2",
+ "dxva2-copy",
+ "d3d11va-copy",
+ "nvdec",
+ "nvdec-copy",
+ "vdpau",
+ "vdpau-copy",
+ "vaapi",
+ "vaapi-copy",
+ 0
};
-#endif
-#if NEW_CUDA_HWACCEL
-static const struct vd_lavc_hwdec mp_vd_lavc_cuda = {
- .type = HWDEC_CUDA,
- .image_format = IMGFMT_CUDA,
- .lavc_suffix = "_cuvid",
- .generic_hwaccel = true,
-};
-#endif
-#if HAVE_CUDA_HWACCEL
-static const struct vd_lavc_hwdec mp_vd_lavc_cuda_copy = {
- .type = HWDEC_CUDA_COPY,
- .lavc_suffix = "_cuvid",
- .copying = true,
-};
-#endif
+static int hwdec_compare(const void *p1, const void *p2)
+{
+ struct hwdec_info *h1 = (void *)p1;
+ struct hwdec_info *h2 = (void *)p2;
-static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = {
- .type = HWDEC_CRYSTALHD,
- .lavc_suffix = "_crystalhd",
- .copying = true,
-};
+ if (h1 == h2)
+ return 0;
-#if HAVE_VAAPI_HWACCEL
-static const struct vd_lavc_hwdec mp_vd_lavc_vaapi = {
- .type = HWDEC_VAAPI,
- .image_format = IMGFMT_VAAPI,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .static_pool = true,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P10, AV_PIX_FMT_P010},
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_NONE}
- },
-};
+ // Strictly put non-preferred hwdecs to the end of the list.
+ if ((h1->auto_pos == INT_MAX) != (h2->auto_pos == INT_MAX))
+ return h1->auto_pos == INT_MAX ? 1 : -1;
+ // List non-copying entries first, so --hwdec=auto takes them.
+ if (h1->copying != h2->copying)
+ return h1->copying ? 1 : -1;
+ // Order by autoprobe preferrence order.
+ if (h1->auto_pos != h2->auto_pos)
+ return h1->auto_pos > h2->auto_pos ? 1 : -1;
+ // Fallback sort order to make sorting stable.
+ return h1->rank > h2->rank ? 1 :-1;
+}
-#include "video/vaapi.h"
-
-static const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy = {
- .type = HWDEC_VAAPI_COPY,
- .copying = true,
- .image_format = IMGFMT_VAAPI,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .static_pool = true,
- .create_dev = va_create_standalone,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P10, AV_PIX_FMT_P010},
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NV12},
- {AV_PIX_FMT_NONE}
- },
-};
-#endif
+// (This takes care of some bookkeeping too, like setting info.name)
+static void add_hwdec_item(struct hwdec_info **infos, int *num_infos,
+ struct hwdec_info info)
+{
+ if (info.copying)
+ mp_snprintf_cat(info.method_name, sizeof(info.method_name), "-copy");
+
+ // (Including the codec name in case this is a wrapper looks pretty dumb,
+ // but better not have them clash with hwaccels and others.)
+ snprintf(info.name, sizeof(info.name), "%s-%s",
+ info.codec->name, info.method_name);
+
+ info.rank = *num_infos;
+ info.auto_pos = INT_MAX;
+ for (int x = 0; hwdec_autoprobe_order[x]; x++) {
+ if (strcmp(hwdec_autoprobe_order[x], info.method_name) == 0)
+ info.auto_pos = x;
+ }
-#if HAVE_VDPAU_HWACCEL
-static const struct vd_lavc_hwdec mp_vd_lavc_vdpau = {
- .type = HWDEC_VDPAU,
- .image_format = IMGFMT_VDPAU,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUV420P},
- {AV_PIX_FMT_NONE}
- },
-};
+ MP_TARRAY_APPEND(NULL, *infos, *num_infos, info);
+}
-#include "video/vdpau.h"
-
-static const struct vd_lavc_hwdec mp_vd_lavc_vdpau_copy = {
- .type = HWDEC_VDPAU_COPY,
- .copying = true,
- .image_format = IMGFMT_VDPAU,
- .generic_hwaccel = true,
- .set_hwframes = true,
- .create_dev = vdpau_create_standalone,
- .pixfmt_map = (const enum AVPixelFormat[][2]) {
- {AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P},
- {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUV420P},
- {AV_PIX_FMT_NONE}
- },
-};
-#endif
+static void add_all_hwdec_methods(struct hwdec_info **infos, int *num_infos)
+{
+ AVCodec *codec = NULL;
+ while (1) {
+ codec = av_codec_next(codec);
+ if (!codec)
+ break;
+ if (codec->type != AVMEDIA_TYPE_VIDEO || !av_codec_is_decoder(codec))
+ continue;
+
+ struct hwdec_info info_template = {
+ .pix_fmt = AV_PIX_FMT_NONE,
+ .codec = codec,
+ };
+
+ const char *wrapper = NULL;
+ if (codec->capabilities & (AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_HYBRID))
+ wrapper = codec->wrapper_name;
+
+ // A decoder can provide multiple methods. In particular, hwaccels
+ // provide various methods (e.g. native h264 with vaapi & d3d11), but
+ // even wrapper decoders could provide multiple methods.
+ bool found_any = false;
+ for (int n = 0; ; n++) {
+ const AVCodecHWConfig *cfg = avcodec_get_hw_config(codec, n);
+ if (!cfg)
+ break;
-static const struct vd_lavc_hwdec *const hwdec_list[] = {
-#if HAVE_RPI
- &mp_vd_lavc_rpi,
- &mp_vd_lavc_rpi_copy,
-#endif
-#if HAVE_VDPAU_HWACCEL
- &mp_vd_lavc_vdpau,
-#endif
-#if HAVE_VIDEOTOOLBOX_HWACCEL
- &mp_vd_lavc_videotoolbox,
- &mp_vd_lavc_videotoolbox_copy,
-#endif
-#if HAVE_D3D_HWACCEL
- &mp_vd_lavc_d3d11va,
-
- #if HAVE_D3D9_HWACCEL
- &mp_vd_lavc_dxva2,
- &mp_vd_lavc_dxva2_copy,
- #endif
- &mp_vd_lavc_d3d11va_copy,
-#endif
-#if HAVE_ANDROID
- &mp_vd_lavc_mediacodec,
-#endif
-#if HAVE_CUDA_HWACCEL
- #if NEW_CUDA_HWACCEL
- &mp_vd_lavc_cuda,
- #else
- &mp_vd_lavc_cuda_old,
- #endif
- &mp_vd_lavc_cuda_copy,
-#endif
-#if HAVE_VDPAU_HWACCEL
- &mp_vd_lavc_vdpau_copy,
-#endif
-#if HAVE_VAAPI_HWACCEL
- &mp_vd_lavc_vaapi,
- &mp_vd_lavc_vaapi_copy,
-#endif
- &mp_vd_lavc_crystalhd,
- NULL
-};
+ if ((cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) ||
+ (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
+ {
+ struct hwdec_info info = info_template;
+ info.lavc_device = cfg->device_type;
+ info.pix_fmt = cfg->pix_fmt;
+
+ const char *name = av_hwdevice_get_type_name(cfg->device_type);
+ assert(name); // API violation by libavcodec
+
+ // nvdec hwaccels and the cuvid full decoder clash with their
+ // naming, so fix it here; we also prefer nvdec for the hwaccel.
+ if (strcmp(name, "cuda") == 0 && !wrapper)
+ name = "nvdec";
+
+ snprintf(info.method_name, sizeof(info.method_name), "%s", name);
+
+ // Usually we want to prefer using hw_frames_ctx for true
+ // hwaccels only, but we actually don't have any way to detect
+ // those, so always use hw_frames_ctx if offered.
+ if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) {
+ info.use_hw_frames = true;
+ } else {
+ info.use_hw_device = true;
+ }
-static struct vd_lavc_hwdec *find_hwcodec(enum hwdec_type api)
-{
- for (int n = 0; hwdec_list[n]; n++) {
- if (hwdec_list[n]->type == api)
- return (struct vd_lavc_hwdec *)hwdec_list[n];
+ // Direct variant.
+ add_hwdec_item(infos, num_infos, info);
+
+ // Copy variant.
+ info.copying = true;
+ if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) {
+ info.use_hw_frames = false;
+ info.use_hw_device = true;
+ }
+ add_hwdec_item(infos, num_infos, info);
+
+ found_any = true;
+ } else if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_INTERNAL) {
+ struct hwdec_info info = info_template;
+ info.pix_fmt = cfg->pix_fmt;
+
+ const char *name = wrapper;
+ if (!name)
+ name = av_get_pix_fmt_name(info.pix_fmt);
+ assert(name); // API violation by libavcodec
+
+ snprintf(info.method_name, sizeof(info.method_name), "%s", name);
+
+ // Direct variant.
+ add_hwdec_item(infos, num_infos, info);
+
+ // Copy variant.
+ info.copying = true;
+ info.pix_fmt = AV_PIX_FMT_NONE; // trust it can do sw output
+ add_hwdec_item(infos, num_infos, info);
+
+ found_any = true;
+ }
+ }
+
+ if (!found_any && wrapper) {
+ // We _know_ there's something supported here, usually outputting
+ // sw surfaces. E.g. mediacodec (before hw_device_ctx support).
+
+ struct hwdec_info info = info_template;
+ info.copying = true; // probably
+
+ snprintf(info.method_name, sizeof(info.method_name), "%s", wrapper);
+ add_hwdec_item(infos, num_infos, info);
+ }
}
- return NULL;
+
+ qsort(*infos, *num_infos, sizeof(struct hwdec_info), hwdec_compare);
}
static bool hwdec_codec_allowed(struct dec_video *vd, const char *codec)
@@ -318,119 +366,139 @@ static bool hwdec_codec_allowed(struct dec_video *vd, const char *codec)
return false;
}
-int hwdec_get_max_refs(struct lavc_ctx *ctx)
+static AVBufferRef *hwdec_create_dev(struct dec_video *vd,
+ struct hwdec_info *hwdec,
+ bool autoprobe)
{
- switch (ctx->avctx->codec_id) {
- case AV_CODEC_ID_H264:
- case AV_CODEC_ID_HEVC:
- return 16;
- case AV_CODEC_ID_VP9:
- return 8;
+ assert(hwdec->lavc_device);
+
+ if (hwdec->copying) {
+ const struct hwcontext_fns *fns =
+ hwdec_get_hwcontext_fns(hwdec->lavc_device);
+ if (fns && fns->create_dev) {
+ struct hwcontext_create_dev_params params = {
+ .probing = autoprobe,
+ };
+ return fns->create_dev(vd->global, vd->log, &params);
+ } else {
+ AVBufferRef* ref = NULL;
+ av_hwdevice_ctx_create(&ref, hwdec->lavc_device, NULL, NULL, 0);
+ return ref;
+ }
+ } else if (vd->hwdec_devs) {
+ hwdec_devices_request_all(vd->hwdec_devs);
+ return hwdec_devices_get_lavc(vd->hwdec_devs, hwdec->lavc_device);
}
- return 2;
-}
-// This is intended to return the name of a decoder for a given wrapper API.
-// Decoder wrappers are usually added to libavcodec with a specific suffix.
-// For example the mmal h264 decoder is named h264_mmal.
-// This API would e.g. return h264_mmal for
-// hwdec_find_decoder("h264", "_mmal").
-// Just concatenating the two names will not always work due to inconsistencies
-// (e.g. "mpeg2video" vs. "mpeg2").
-static const char *hwdec_find_decoder(const char *codec, const char *suffix)
-{
- enum AVCodecID codec_id = mp_codec_to_av_codec_id(codec);
- if (codec_id == AV_CODEC_ID_NONE)
- return NULL;
- AVCodec *cur = NULL;
- for (;;) {
- cur = av_codec_next(cur);
- if (!cur)
- break;
- if (cur->id == codec_id && av_codec_is_decoder(cur) &&
- bstr_endswith0(bstr0(cur->name), suffix))
- return cur->name;
- }
return NULL;
}
-// Parallel to hwdec_find_decoder(): return whether a hwdec can use the given
-// decoder. This can't be answered accurately; it works for wrapper decoders
-// only (like mmal), and for real hwaccels this will always return false.
-static bool hwdec_is_wrapper(struct vd_lavc_hwdec *hwdec, const char *decoder)
+// Select if and which hwdec to use. Also makes sure to get the decode device.
+static void select_and_set_hwdec(struct dec_video *vd)
{
- if (!hwdec->lavc_suffix)
- return false;
- return bstr_endswith0(bstr0(decoder), hwdec->lavc_suffix);
-}
+ vd_ffmpeg_ctx *ctx = vd->priv;
+ const char *codec = vd->codec->codec;
-static struct mp_hwdec_ctx *hwdec_create_dev(struct dec_video *vd,
- struct vd_lavc_hwdec *hwdec,
- bool autoprobe)
-{
- if (hwdec->create_dev)
- return hwdec->create_dev(vd->global, vd->log, autoprobe);
- if (vd->hwdec_devs) {
- hwdec_devices_request(vd->hwdec_devs, hwdec->type);
- return hwdec_devices_get(vd->hwdec_devs, hwdec->type);
- }
- return NULL;
-}
+ bstr opt = bstr0(vd->opts->hwdec_api);
-static int hwdec_probe(struct dec_video *vd, struct vd_lavc_hwdec *hwdec,
- const char *codec, bool autoprobe)
-{
- vd_ffmpeg_ctx *ctx = vd->priv;
- int r = 0;
- if (hwdec->probe)
- r = hwdec->probe(ctx, hwdec, codec);
- if (hwdec->generic_hwaccel) {
- assert(!hwdec->probe && !hwdec->init && !hwdec->init_decoder &&
- !hwdec->uninit && !hwdec->allocate_image);
- struct mp_hwdec_ctx *dev = hwdec_create_dev(vd, hwdec, autoprobe);
- if (!dev)
- return hwdec->copying ? -1 : HWDEC_ERR_NO_CTX;
- if (dev->emulated)
- r = HWDEC_ERR_EMULATED;
- if (hwdec->create_dev && dev->destroy)
- dev->destroy(dev);
+ bool hwdec_requested = !bstr_equals0(opt, "no");
+ bool hwdec_auto_all = bstr_equals0(opt, "auto") ||
+ bstr_equals0(opt, "yes") ||
+ bstr_equals0(opt, "");
+ bool hwdec_auto_copy = bstr_equals0(opt, "auto-copy");
+ bool hwdec_auto = hwdec_auto_all || hwdec_auto_copy;
+
+ if (hwdec_codec_allowed(vd, codec) && hwdec_requested) {
+ struct hwdec_info *hwdecs = NULL;
+ int num_hwdecs = 0;
+ add_all_hwdec_methods(&hwdecs, &num_hwdecs);
+
+ ctx->hwdec_requested = true;
+
+ for (int n = 0; n < num_hwdecs; n++) {
+ struct hwdec_info *hwdec = &hwdecs[n];
+
+ const char *hw_codec = mp_codec_from_av_codec_id(hwdec->codec->id);
+ if (!hw_codec || strcmp(hw_codec, codec) != 0)
+ continue;
+
+ if (!hwdec_auto && !(bstr_equals0(opt, hwdec->method_name) ||
+ bstr_equals0(opt, hwdec->name)))
+ continue;
+
+ MP_VERBOSE(vd, "Looking at hwdec %s...\n", hwdec->name);
+
+ if (hwdec_auto_copy && !hwdec->copying) {
+ MP_VERBOSE(vd, "Not using this for auto-copy.\n");
+ continue;
+ }
+
+ if (hwdec->lavc_device) {
+ ctx->hwdec_dev = hwdec_create_dev(vd, hwdec, hwdec_auto);
+ if (!ctx->hwdec_dev) {
+ MP_VERBOSE(vd, "Could not create device.\n");
+ continue;
+ }
+
+ const struct hwcontext_fns *fns =
+ hwdec_get_hwcontext_fns(hwdec->lavc_device);
+ if (fns && fns->is_emulated && fns->is_emulated(ctx->hwdec_dev)) {
+ if (hwdec_auto) {
+ MP_VERBOSE(vd, "Not using emulated API.\n");
+ av_buffer_unref(&ctx->hwdec_dev);
+ continue;
+ }
+ MP_WARN(vd, "Using emulated hardware decoding API.\n");
+ }
+ } else if (!hwdec->copying) {
+ // Most likely METHOD_INTERNAL, which often use delay-loaded
+ // VO support as well.
+ hwdec_devices_request_all(vd->hwdec_devs);
+ }
+
+ ctx->use_hwdec = true;
+ ctx->hwdec = *hwdec;
+ break;
+ }
+
+ talloc_free(hwdecs);
+ } else {
+ MP_VERBOSE(vd, "Not trying to use hardware decoding: codec %s is not "
+ "on whitelist, or does not support hardware acceleration.\n",
+ codec);
}
- if (r >= 0) {
- if (hwdec->lavc_suffix && !hwdec_find_decoder(codec, hwdec->lavc_suffix))
- return HWDEC_ERR_NO_CODEC;
+
+ if (ctx->use_hwdec) {
+ MP_VERBOSE(vd, "Trying hardware decoding via %s.\n", ctx->hwdec.name);
+ if (strcmp(ctx->decoder, ctx->hwdec.codec->name) != 0)
+ MP_VERBOSE(vd, "Using underlying hw-decoder '%s'\n",
+ ctx->hwdec.codec->name);
+ } else {
+ MP_VERBOSE(vd, "Using software decoding.\n");
}
- return r;
}
-static struct vd_lavc_hwdec *probe_hwdec(struct dec_video *vd, bool autoprobe,
- enum hwdec_type api,
- const char *codec)
+int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param)
{
- MP_VERBOSE(vd, "Probing '%s'...\n", m_opt_choice_str(mp_hwdec_names, api));
- struct vd_lavc_hwdec *hwdec = find_hwcodec(api);
- if (!hwdec) {
- int level = autoprobe ? MSGL_V : MSGL_WARN;
- MP_MSG(vd, level, "Requested hardware decoder not compiled.\n");
- return NULL;
- }
- int r = hwdec_probe(vd, hwdec, codec, autoprobe);
- if (r == HWDEC_ERR_EMULATED) {
- if (autoprobe)
- return NULL;
- // User requested this explicitly.
- MP_WARN(vd, "Using emulated hardware decoding API.\n");
- r = 0;
- }
- if (r >= 0) {
- return hwdec;
- } else if (r == HWDEC_ERR_NO_CODEC) {
- MP_VERBOSE(vd, "Hardware decoder for '%s' with the given API not found "
- "in libavcodec.\n", codec);
- } else if (r == HWDEC_ERR_NO_CTX && !autoprobe) {
- MP_WARN(vd, "VO does not support requested hardware decoder, or "
- "loading it failed.\n");
+ if (bstr_equals0(param, "help")) {
+ struct hwdec_info *hwdecs = NULL;
+ int num_hwdecs = 0;
+ add_all_hwdec_methods(&hwdecs, &num_hwdecs);
+
+ mp_info(log, "Valid values (with alternative full names):\n");
+
+ for (int n = 0; n < num_hwdecs; n++) {
+ struct hwdec_info *hwdec = &hwdecs[n];
+
+ mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name);
+ }
+
+ talloc_free(hwdecs);
+
+ return M_OPT_EXIT;
}
- return NULL;
+ return 0;
}
static void uninit(struct dec_video *vd)
@@ -450,72 +518,20 @@ static void force_fallback(struct dec_video *vd)
uninit_avctx(vd);
int lev = ctx->hwdec_notified ? MSGL_WARN : MSGL_V;
mp_msg(vd->log, lev, "Falling back to software decoding.\n");
- init_avctx(vd, ctx->decoder, NULL);
+ init_avctx(vd);
}
static void reinit(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
- const char *decoder = ctx->decoder;
- const char *codec = vd->codec->codec;
uninit_avctx(vd);
- struct vd_lavc_hwdec *hwdec = NULL;
-
- if (hwdec_codec_allowed(vd, codec)) {
- int api = vd->opts->hwdec_api;
- if (HWDEC_IS_AUTO(api)) {
- // If a specific decoder is forced, we should try a hwdec method
- // that works with it, instead of simply failing later at runtime.
- // This is good for avoiding trying "normal" hwaccels on wrapper
- // decoders (like vaapi on a mmal decoder). Since libavcodec doesn't
- // tell us which decoder supports which hwaccel methods without
- // actually running it, do it by detecting such wrapper decoders.
- // On the other hand, e.g. "--hwdec=rpi" should always force the
- // wrapper decoder, so be careful not to break this case.
- bool might_be_wrapper = false;
- for (int n = 0; hwdec_list[n]; n++) {
- struct vd_lavc_hwdec *other = (void *)hwdec_list[n];
- if (hwdec_is_wrapper(other, decoder))
- might_be_wrapper = true;
- }
- for (int n = 0; hwdec_list[n]; n++) {
- hwdec = probe_hwdec(vd, true, hwdec_list[n]->type, codec);
- if (hwdec) {
- if (might_be_wrapper && !hwdec_is_wrapper(hwdec, decoder)) {
- MP_VERBOSE(vd, "This hwaccel is not compatible.\n");
- continue;
- }
- if (api == HWDEC_AUTO_COPY && !hwdec->copying) {
- MP_VERBOSE(vd, "Not using this for auto-copy mode.\n");
- continue;
- }
- break;
- }
- }
- } else if (api != HWDEC_NONE) {
- hwdec = probe_hwdec(vd, false, api, codec);
- }
- } else {
- MP_VERBOSE(vd, "Not trying to use hardware decoding: codec %s is not "
- "on whitelist, or does not support hardware acceleration.\n",
- codec);
- }
-
- if (hwdec) {
- const char *orig_decoder = decoder;
- if (hwdec->lavc_suffix)
- decoder = hwdec_find_decoder(codec, hwdec->lavc_suffix);
- MP_VERBOSE(vd, "Trying hardware decoding.\n");
- if (strcmp(orig_decoder, decoder) != 0)
- MP_VERBOSE(vd, "Using underlying hw-decoder '%s'\n", decoder);
- } else {
- MP_VERBOSE(vd, "Using software decoding.\n");
- }
+ select_and_set_hwdec(vd);
- init_avctx(vd, decoder, hwdec);
- if (!ctx->avctx && hwdec)
+ bool use_hwdec = ctx->use_hwdec;
+ init_avctx(vd);
+ if (!ctx->avctx && use_hwdec)
force_fallback(vd);
}
@@ -541,8 +557,7 @@ static int init(struct dec_video *vd, const char *decoder)
return 1;
}
-static void init_avctx(struct dec_video *vd, const char *decoder,
- struct vd_lavc_hwdec *hwdec)
+static void init_avctx(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
struct vd_lavc_params *lavc_param = vd->opts->vd_lavc_params;
@@ -550,19 +565,25 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
assert(!ctx->avctx);
- AVCodec *lavc_codec = avcodec_find_decoder_by_name(decoder);
+ const AVCodec *lavc_codec = NULL;
+
+ if (ctx->use_hwdec) {
+ lavc_codec = ctx->hwdec.codec;
+ } else {
+ lavc_codec = avcodec_find_decoder_by_name(ctx->decoder);
+ }
if (!lavc_codec)
return;
+ const AVCodecDescriptor *desc = avcodec_descriptor_get(lavc_codec->id);
+ ctx->intra_only = desc && (desc->props & AV_CODEC_PROP_INTRA_ONLY);
+
ctx->codec_timebase = mp_get_codec_timebase(vd->codec);
// This decoder does not read pkt_timebase correctly yet.
- if (strstr(decoder, "_mmal"))
+ if (strstr(lavc_codec->name, "_mmal"))
ctx->codec_timebase = (AVRational){1, 1000000};
- ctx->pix_fmt = AV_PIX_FMT_NONE;
- ctx->hwdec = hwdec;
- ctx->hwdec_fmt = 0;
ctx->hwdec_failed = false;
ctx->hwdec_request_reinit = false;
ctx->avctx = avcodec_alloc_context3(lavc_codec);
@@ -580,44 +601,36 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
if (!ctx->pic)
goto error;
- if (ctx->hwdec) {
+ if (ctx->use_hwdec) {
avctx->opaque = vd;
avctx->thread_count = 1;
-#if HAVE_VDPAU_HWACCEL
avctx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
-#endif
-#ifdef AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH
if (!lavc_param->check_hw_profile)
avctx->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
-#endif
- if (ctx->hwdec->image_format)
- avctx->get_format = get_format_hwdec;
- if (ctx->hwdec->allocate_image)
- avctx->get_buffer2 = get_buffer2_hwdec;
- if (ctx->hwdec->init && ctx->hwdec->init(ctx) < 0)
- goto error;
- if (ctx->hwdec->generic_hwaccel) {
- ctx->hwdec_dev = hwdec_create_dev(vd, ctx->hwdec, false);
- if (!ctx->hwdec_dev)
+
+ if (ctx->hwdec.use_hw_device) {
+ if (ctx->hwdec_dev)
+ avctx->hw_device_ctx = av_buffer_ref(ctx->hwdec_dev);
+ if (!avctx->hw_device_ctx)
goto error;
- ctx->owns_hwdec_dev = !!ctx->hwdec->create_dev;
- if (ctx->hwdec_dev->restore_device)
- ctx->hwdec_dev->restore_device(ctx->hwdec_dev);
- if (!ctx->hwdec->set_hwframes) {
-#if HAVE_VDPAU_HWACCEL
- avctx->hw_device_ctx = av_buffer_ref(ctx->hwdec_dev->av_device_ref);
-#else
+ }
+ if (ctx->hwdec.use_hw_frames) {
+ if (!ctx->hwdec_dev)
goto error;
-#endif
- }
}
- ctx->max_delay_queue = ctx->hwdec->delay_queue;
+
+ if (ctx->hwdec.pix_fmt != AV_PIX_FMT_NONE)
+ avctx->get_format = get_format_hwdec;
+
+ // Some APIs benefit from this, for others it's additional bloat.
+ if (ctx->hwdec.copying)
+ ctx->max_delay_queue = HWDEC_DELAY_QUEUE_COUNT;
ctx->hw_probing = true;
} else {
mp_set_avcodec_threads(vd->log, avctx, lavc_param->threads);
}
- if (!ctx->hwdec && vd->vo && lavc_param->dr) {
+ if (!ctx->use_hwdec && vd->vo && lavc_param->dr) {
avctx->opaque = vd;
avctx->get_buffer2 = get_buffer2_direct;
avctx->thread_safe_callbacks = 1;
@@ -691,186 +704,91 @@ static void uninit_avctx(struct dec_video *vd)
av_frame_free(&ctx->pic);
av_buffer_unref(&ctx->cached_hw_frames_ctx);
- if (ctx->hwdec && ctx->hwdec->uninit)
- ctx->hwdec->uninit(ctx);
- ctx->hwdec = NULL;
- assert(ctx->hwdec_priv == NULL);
-
avcodec_free_context(&ctx->avctx);
- if (ctx->hwdec_dev && ctx->owns_hwdec_dev && ctx->hwdec_dev->destroy)
- ctx->hwdec_dev->destroy(ctx->hwdec_dev);
- ctx->hwdec_dev = NULL;
- ctx->owns_hwdec_dev = false;
+ av_buffer_unref(&ctx->hwdec_dev);
ctx->hwdec_failed = false;
ctx->hwdec_fail_count = 0;
ctx->max_delay_queue = 0;
ctx->hw_probing = false;
+ ctx->hwdec = (struct hwdec_info){0};
+ ctx->use_hwdec = false;
}
-static void update_image_params(struct dec_video *vd, AVFrame *frame,
- struct mp_image_params *params)
+static int init_generic_hwaccel(struct dec_video *vd, enum AVPixelFormat hw_fmt)
{
- vd_ffmpeg_ctx *ctx = vd->priv;
- AVFrameSideData *sd;
-
-#if HAVE_AVUTIL_CONTENT_LIGHT_LEVEL
- // Get the content light metadata if available
- sd = av_frame_get_side_data(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
- if (sd) {
- AVContentLightMetadata *clm = (AVContentLightMetadata *)sd->data;
- params->color.sig_peak = clm->MaxCLL / MP_REF_WHITE;
- }
-#endif
-
-#if LIBAVCODEC_VERSION_MICRO >= 100
- // Otherwise, try getting the mastering metadata if available
- sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
- if (!params->color.sig_peak && sd) {
- AVMasteringDisplayMetadata *mdm = (AVMasteringDisplayMetadata *)sd->data;
- if (mdm->has_luminance)
- params->color.sig_peak = av_q2d(mdm->max_luminance) / MP_REF_WHITE;
- }
-#endif
-
- if (params->color.sig_peak) {
- ctx->cached_sig_peak = params->color.sig_peak;
- } else {
- params->color.sig_peak = ctx->cached_sig_peak;
- }
-
- params->rotate = vd->codec->rotate;
- params->stereo_in = vd->codec->stereo_mode;
-}
+ struct lavc_ctx *ctx = vd->priv;
+ AVBufferRef *new_frames_ctx = NULL;
-// Allocate and set AVCodecContext.hw_frames_ctx. Also caches them on redundant
-// calls (useful because seeks issue get_format, which clears hw_frames_ctx).
-// device_ctx: reference to an AVHWDeviceContext
-// av_sw_format: AV_PIX_FMT_ for the underlying hardware frame format
-// initial_pool_size: number of frames in the memory pool on creation
-// Return >=0 on success, <0 on error.
-int hwdec_setup_hw_frames_ctx(struct lavc_ctx *ctx, AVBufferRef *device_ctx,
- int av_sw_format, int initial_pool_size)
-{
- int w = ctx->avctx->coded_width;
- int h = ctx->avctx->coded_height;
- int av_hw_format = imgfmt2pixfmt(ctx->hwdec_fmt);
+ if (!ctx->hwdec.use_hw_frames)
+ return 0;
- if (!device_ctx) {
+ if (!ctx->hwdec_dev) {
MP_ERR(ctx, "Missing device context.\n");
- return -1;
+ goto error;
}
- if (ctx->cached_hw_frames_ctx) {
- AVHWFramesContext *fctx = (void *)ctx->cached_hw_frames_ctx->data;
- if (fctx->width != w || fctx->height != h ||
- fctx->sw_format != av_sw_format ||
- fctx->format != av_hw_format)
- {
- av_buffer_unref(&ctx->cached_hw_frames_ctx);
- }
+ if (avcodec_get_hw_frames_parameters(ctx->avctx,
+ ctx->hwdec_dev, hw_fmt, &new_frames_ctx) < 0)
+ {
+ MP_VERBOSE(ctx, "Hardware decoding of this stream is unsupported?\n");
+ goto error;
}
- if (!ctx->cached_hw_frames_ctx) {
- ctx->cached_hw_frames_ctx = av_hwframe_ctx_alloc(device_ctx);
- if (!ctx->cached_hw_frames_ctx)
- return -1;
+ AVHWFramesContext *new_fctx = (void *)new_frames_ctx->data;
- AVHWFramesContext *fctx = (void *)ctx->cached_hw_frames_ctx->data;
+#if LIBAVCODEC_VERSION_MICRO >= 100
+ if (ctx->hwdec.pix_fmt == AV_PIX_FMT_VIDEOTOOLBOX)
+ new_fctx->sw_format = imgfmt2pixfmt(vd->opts->videotoolbox_format);
+#endif
+ if (vd->opts->hwdec_image_format)
+ new_fctx->sw_format = imgfmt2pixfmt(vd->opts->hwdec_image_format);
- fctx->format = av_hw_format;
- fctx->sw_format = av_sw_format;
- fctx->width = w;
- fctx->height = h;
+ // 1 surface is already included by libavcodec. The field is 0 if the
+ // hwaccel supports dynamic surface allocation.
+ if (new_fctx->initial_pool_size)
+ new_fctx->initial_pool_size += HWDEC_EXTRA_SURFACES - 1;
- fctx->initial_pool_size = initial_pool_size;
+ const struct hwcontext_fns *fns =
+ hwdec_get_hwcontext_fns(new_fctx->device_ctx->type);
- if (ctx->hwdec->hwframes_refine)
- ctx->hwdec->hwframes_refine(ctx, ctx->cached_hw_frames_ctx);
+ if (fns && fns->refine_hwframes)
+ fns->refine_hwframes(new_frames_ctx);
- int res = av_hwframe_ctx_init(ctx->cached_hw_frames_ctx);
- if (res < 0) {
- MP_ERR(ctx, "Failed to allocate hw frames.\n");
+ // We might be able to reuse a previously allocated frame pool.
+ if (ctx->cached_hw_frames_ctx) {
+ AVHWFramesContext *old_fctx = (void *)ctx->cached_hw_frames_ctx->data;
+
+ if (new_fctx->format != old_fctx->format ||
+ new_fctx->sw_format != old_fctx->sw_format ||
+ new_fctx->width != old_fctx->width ||
+ new_fctx->height != old_fctx->height ||
+ new_fctx->initial_pool_size != old_fctx->initial_pool_size)
av_buffer_unref(&ctx->cached_hw_frames_ctx);
- return -1;
- }
}
- assert(!ctx->avctx->hw_frames_ctx);
- ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->cached_hw_frames_ctx);
- return ctx->avctx->hw_frames_ctx ? 0 : -1;
-}
-
-static int init_generic_hwaccel(struct dec_video *vd)
-{
- struct lavc_ctx *ctx = vd->priv;
- struct vd_lavc_hwdec *hwdec = ctx->hwdec;
-
- if (!ctx->hwdec_dev)
- return -1;
-
- if (!hwdec->set_hwframes)
- return 0;
-
- // libavcodec has no way yet to communicate the exact surface format needed
- // for the frame pool, or the required minimum size of the frame pool.
- // Hopefully, this weakness in the libavcodec API will be fixed in the
- // future.
- // For the pixel format, we try to second-guess from what the libavcodec
- // software decoder would require (sw_pix_fmt). It could break and require
- // adjustment if new hwaccel surface formats are added.
- enum AVPixelFormat av_sw_format = AV_PIX_FMT_NONE;
- assert(hwdec->pixfmt_map);
- for (int n = 0; hwdec->pixfmt_map[n][0] != AV_PIX_FMT_NONE; n++) {
- if (ctx->avctx->sw_pix_fmt == hwdec->pixfmt_map[n][0]) {
- av_sw_format = hwdec->pixfmt_map[n][1];
- break;
+ if (!ctx->cached_hw_frames_ctx) {
+ if (av_hwframe_ctx_init(new_frames_ctx) < 0) {
+ MP_ERR(ctx, "Failed to allocate hw frames.\n");
+ goto error;
}
- }
- if (hwdec->image_format == IMGFMT_VIDEOTOOLBOX)
- av_sw_format = imgfmt2pixfmt(vd->opts->videotoolbox_format);
-
- if (av_sw_format == AV_PIX_FMT_NONE) {
- MP_VERBOSE(ctx, "Unsupported hw decoding format: %s\n",
- mp_imgfmt_to_name(pixfmt2imgfmt(ctx->avctx->sw_pix_fmt)));
- return -1;
- }
-
- // The video output might not support all formats.
- // Note that supported_formats==NULL means any are accepted.
- int *render_formats = ctx->hwdec_dev->supported_formats;
- if (render_formats) {
- int mp_format = pixfmt2imgfmt(av_sw_format);
- bool found = false;
- for (int n = 0; render_formats[n]; n++) {
- if (render_formats[n] == mp_format) {
- found = true;
- break;
- }
- }
- if (!found) {
- MP_WARN(ctx, "Surface format %s not supported for direct rendering.\n",
- mp_imgfmt_to_name(mp_format));
- return -1;
- }
+ ctx->cached_hw_frames_ctx = new_frames_ctx;
+ new_frames_ctx = NULL;
}
- int pool_size = 0;
- if (hwdec->static_pool)
- pool_size = hwdec_get_max_refs(ctx) + HWDEC_EXTRA_SURFACES;
-
- ctx->hwdec_fmt = hwdec->image_format;
+ ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->cached_hw_frames_ctx);
+ if (!ctx->avctx->hw_frames_ctx)
+ goto error;
- if (hwdec->image_format == IMGFMT_VDPAU &&
- ctx->avctx->codec_id == AV_CODEC_ID_HEVC)
- {
- MP_WARN(ctx, "HEVC video output may be broken due to nVidia bugs.\n");
- }
+ av_buffer_unref(&new_frames_ctx);
+ return 0;
- return hwdec_setup_hw_frames_ctx(ctx, ctx->hwdec_dev->av_device_ref,
- av_sw_format, pool_size);
+error:
+ av_buffer_unref(&new_frames_ctx);
+ av_buffer_unref(&ctx->cached_hw_frames_ctx);
+ return -1;
}
static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
@@ -888,41 +806,16 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
MP_VERBOSE(vd, "Codec profile: %s (0x%x)\n", profile ? profile : "unknown",
avctx->profile);
- assert(ctx->hwdec);
+ assert(ctx->use_hwdec);
ctx->hwdec_request_reinit |= ctx->hwdec_failed;
ctx->hwdec_failed = false;
enum AVPixelFormat select = AV_PIX_FMT_NONE;
for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) {
- if (ctx->hwdec->image_format == pixfmt2imgfmt(fmt[i])) {
- if (ctx->hwdec->generic_hwaccel) {
- if (init_generic_hwaccel(vd) < 0)
- break;
- select = fmt[i];
+ if (ctx->hwdec.pix_fmt == fmt[i]) {
+ if (init_generic_hwaccel(vd, fmt[i]) < 0)
break;
- }
- // There could be more reasons for a change, and it's possible
- // that we miss some. (Might also depend on the hwaccel type.)
- bool change =
- ctx->hwdec_w != avctx->coded_width ||
- ctx->hwdec_h != avctx->coded_height ||
- ctx->hwdec_fmt != ctx->hwdec->image_format ||
- ctx->hwdec_profile != avctx->profile ||
- ctx->hwdec_request_reinit ||
- ctx->hwdec->volatile_context;
- ctx->hwdec_w = avctx->coded_width;
- ctx->hwdec_h = avctx->coded_height;
- ctx->hwdec_fmt = ctx->hwdec->image_format;
- ctx->hwdec_profile = avctx->profile;
- ctx->hwdec_request_reinit = false;
- if (change && ctx->hwdec->init_decoder) {
- if (ctx->hwdec->init_decoder(ctx, ctx->hwdec_w, ctx->hwdec_h) < 0)
- {
- ctx->hwdec_fmt = 0;
- break;
- }
- }
select = fmt[i];
break;
}
@@ -979,16 +872,16 @@ static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags)
p->dr_w = w;
p->dr_h = h;
p->dr_stride_align = stride_align;
- MP_VERBOSE(p, "DR parameter change to %dx%d %s align=%d\n", w, h,
- mp_imgfmt_to_name(imgfmt), stride_align);
+ MP_DBG(p, "DR parameter change to %dx%d %s align=%d\n", w, h,
+ mp_imgfmt_to_name(imgfmt), stride_align);
}
struct mp_image *img = mp_image_pool_get_no_alloc(p->dr_pool, imgfmt, w, h);
if (!img) {
- MP_VERBOSE(p, "Allocating new DR image...\n");
+ MP_DBG(p, "Allocating new DR image...\n");
img = vo_get_image(vd->vo, imgfmt, w, h, stride_align);
if (!img) {
- MP_VERBOSE(p, "...failed..\n");
+ MP_DBG(p, "...failed..\n");
goto fallback;
}
@@ -1025,45 +918,6 @@ fallback:
return avcodec_default_get_buffer2(avctx, pic, flags);
}
-static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
-{
- struct dec_video *vd = avctx->opaque;
- vd_ffmpeg_ctx *ctx = vd->priv;
-
- int imgfmt = pixfmt2imgfmt(pic->format);
- if (!ctx->hwdec || ctx->hwdec_fmt != imgfmt)
- ctx->hwdec_failed = true;
-
- /* Hardware decoding failed, and we will trigger a proper fallback later
- * when returning from the decode call. (We are forcing complete
- * reinitialization later to reset the thread count properly.)
- */
- if (ctx->hwdec_failed)
- return avcodec_default_get_buffer2(avctx, pic, flags);
-
- // We expect it to use the exact size used to create the hw decoder in
- // get_format_hwdec(). For cropped video, this is expected to be the
- // uncropped size (usually coded_width/coded_height).
- int w = pic->width;
- int h = pic->height;
-
- if (imgfmt != ctx->hwdec_fmt && w != ctx->hwdec_w && h != ctx->hwdec_h)
- return AVERROR(EINVAL);
-
- struct mp_image *mpi = ctx->hwdec->allocate_image(ctx, w, h);
- if (!mpi)
- return AVERROR(ENOMEM);
-
- for (int i = 0; i < 4; i++) {
- pic->data[i] = mpi->planes[i];
- pic->buf[i] = mpi->bufs[i];
- mpi->bufs[i] = NULL;
- }
- talloc_free(mpi);
-
- return 0;
-}
-
static bool prepare_decoding(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
@@ -1074,12 +928,15 @@ static bool prepare_decoding(struct dec_video *vd)
return false;
int drop = ctx->framedrop_flags;
- if (drop) {
- // hr-seek framedrop vs. normal framedrop
- avctx->skip_frame = drop == 2 ? AVDISCARD_NONREF : opts->framedrop;
+ if (drop == 1) {
+ avctx->skip_frame = opts->framedrop; // normal framedrop
+ } else if (drop == 2) {
+ avctx->skip_frame = AVDISCARD_NONREF; // hr-seek framedrop
+ // Can be much more aggressive for true intra codecs.
+ if (ctx->intra_only)
+ avctx->skip_frame = AVDISCARD_ALL;
} else {
- // normal playback
- avctx->skip_frame = ctx->skip_frame;
+ avctx->skip_frame = ctx->skip_frame; // normal playback
}
if (ctx->hwdec_request_reinit)
@@ -1095,14 +952,9 @@ static void handle_err(struct dec_video *vd)
MP_WARN(vd, "Error while decoding frame!\n");
- if (ctx->hwdec) {
+ if (ctx->use_hwdec) {
ctx->hwdec_fail_count += 1;
- // The FFmpeg VT hwaccel is buggy and can crash after 1 broken frame.
- bool force = false;
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 82, 101)
- force |= ctx->hwdec && ctx->hwdec->type == HWDEC_VIDEOTOOLBOX;
-#endif
- if (ctx->hwdec_fail_count >= opts->software_fallback || force)
+ if (ctx->hwdec_fail_count >= opts->software_fallback)
ctx->hwdec_failed = true;
}
}
@@ -1115,6 +967,9 @@ static bool do_send_packet(struct dec_video *vd, struct demux_packet *pkt)
if (!prepare_decoding(vd))
return false;
+ if (avctx->skip_frame == AVDISCARD_ALL)
+ return true;
+
AVPacket avpkt;
mp_set_av_packet(&avpkt, pkt, &ctx->codec_timebase);
@@ -1191,17 +1046,9 @@ static bool decode_frame(struct dec_video *vd)
#if LIBAVCODEC_VERSION_MICRO >= 100
mpi->pkt_duration =
- mp_pts_from_av(av_frame_get_pkt_duration(ctx->pic), &ctx->codec_timebase);
-#endif
-
-#if HAVE_AVUTIL_ICC_PROFILE
- sd = av_frame_get_side_data(ctx->pic, AV_FRAME_DATA_ICC_PROFILE);
- if (sd)
- mpi->icc_profile = av_buffer_ref(sd->buf);
+ mp_pts_from_av(ctx->pic->pkt_duration, &ctx->codec_timebase);
#endif
- update_image_params(vd, ctx->pic, &mpi->params);
-
av_frame_unref(ctx->pic);
MP_TARRAY_APPEND(ctx, ctx->delay_queue, ctx->num_delay_queue, mpi);
@@ -1238,15 +1085,11 @@ static bool receive_frame(struct dec_video *vd, struct mp_image **out_image)
struct mp_image *res = ctx->delay_queue[0];
MP_TARRAY_REMOVE_AT(ctx->delay_queue, ctx->num_delay_queue, 0);
- if (ctx->hwdec && ctx->hwdec->process_image)
- res = ctx->hwdec->process_image(ctx, res);
-
res = res ? mp_img_swap_to_native(res) : NULL;
if (!res)
return progress;
- if (ctx->hwdec && ctx->hwdec->copying && (res->fmt.flags & MP_IMGFLAG_HWACCEL))
- {
+ if (ctx->use_hwdec && ctx->hwdec.copying && res->hwctx) {
struct mp_image *sw = mp_image_hw_download(res, ctx->hwdec_swpool);
mp_image_unrefp(&res);
res = sw;
@@ -1258,10 +1101,10 @@ static bool receive_frame(struct dec_video *vd, struct mp_image **out_image)
}
}
- if (!ctx->hwdec_notified && vd->opts->hwdec_api != HWDEC_NONE) {
- if (ctx->hwdec) {
+ if (!ctx->hwdec_notified && ctx->hwdec_requested) {
+ if (ctx->use_hwdec) {
MP_INFO(vd, "Using hardware decoding (%s).\n",
- m_opt_choice_str(mp_hwdec_names, ctx->hwdec->type));
+ ctx->hwdec.method_name);
} else {
MP_VERBOSE(vd, "Using software decoding.\n");
}
@@ -1293,17 +1136,17 @@ static int control(struct dec_video *vd, int cmd, void *arg)
AVCodecContext *avctx = ctx->avctx;
if (!avctx)
break;
- if (ctx->hwdec && ctx->hwdec->type == HWDEC_RPI)
+ if (ctx->use_hwdec && strcmp(ctx->hwdec.method_name, "mmal"))
break; // MMAL has arbitrary buffering, thus unknown
*(int *)arg = avctx->has_b_frames;
return CONTROL_TRUE;
}
case VDCTRL_GET_HWDEC: {
- *(int *)arg = ctx->hwdec ? ctx->hwdec->type : 0;
+ *(char **)arg = ctx->use_hwdec ? ctx->hwdec.method_name : NULL;
return CONTROL_TRUE;
}
case VDCTRL_FORCE_HWDEC_FALLBACK:
- if (ctx->hwdec) {
+ if (ctx->use_hwdec) {
force_fallback(vd);
return ctx->avctx ? CONTROL_OK : CONTROL_ERROR;
}
diff --git a/video/filter/vf.c b/video/filter/vf.c
index ae38947..48a6e9a 100644
--- a/video/filter/vf.c
+++ b/video/filter/vf.c
@@ -39,50 +39,24 @@
#include "video/mp_image_pool.h"
#include "vf.h"
-extern const vf_info_t vf_info_crop;
-extern const vf_info_t vf_info_expand;
-extern const vf_info_t vf_info_scale;
extern const vf_info_t vf_info_format;
-extern const vf_info_t vf_info_noformat;
-extern const vf_info_t vf_info_flip;
-extern const vf_info_t vf_info_rotate;
-extern const vf_info_t vf_info_mirror;
-extern const vf_info_t vf_info_gradfun;
-extern const vf_info_t vf_info_dsize;
-extern const vf_info_t vf_info_pullup;
extern const vf_info_t vf_info_sub;
-extern const vf_info_t vf_info_yadif;
-extern const vf_info_t vf_info_stereo3d;
+extern const vf_info_t vf_info_convert;
extern const vf_info_t vf_info_lavfi;
extern const vf_info_t vf_info_lavfi_bridge;
extern const vf_info_t vf_info_vaapi;
extern const vf_info_t vf_info_vapoursynth;
extern const vf_info_t vf_info_vapoursynth_lazy;
extern const vf_info_t vf_info_vdpaupp;
-extern const vf_info_t vf_info_buffer;
extern const vf_info_t vf_info_d3d11vpp;
// list of available filters:
static const vf_info_t *const filter_list[] = {
- &vf_info_crop,
- &vf_info_expand,
- &vf_info_scale,
&vf_info_format,
- &vf_info_noformat,
- &vf_info_flip,
-
- &vf_info_mirror,
+ &vf_info_sub,
+ &vf_info_convert,
&vf_info_lavfi,
&vf_info_lavfi_bridge,
- &vf_info_rotate,
- &vf_info_gradfun,
- &vf_info_pullup,
- &vf_info_yadif,
- &vf_info_stereo3d,
-
- &vf_info_dsize,
- &vf_info_sub,
- &vf_info_buffer,
#if HAVE_VAPOURSYNTH_CORE && HAVE_VAPOURSYNTH
&vf_info_vapoursynth,
#endif
@@ -552,7 +526,7 @@ static void query_formats(uint8_t *fmts, struct vf_instance *vf)
static bool is_conv_filter(struct vf_instance *vf)
{
- return vf && (strcmp(vf->info->name, "scale") == 0 || vf->autoinserted);
+ return vf && (strcmp(vf->info->name, "convert") == 0 || vf->autoinserted);
}
static const char *find_conv_filter(uint8_t *fmts_in, uint8_t *fmts_out)
@@ -568,7 +542,7 @@ static const char *find_conv_filter(uint8_t *fmts_in, uint8_t *fmts_out)
}
}
}
- return "scale";
+ return "convert";
}
static void update_formats(struct vf_chain *c, struct vf_instance *vf,
diff --git a/video/filter/vf_buffer.c b/video/filter/vf_buffer.c
deleted file mode 100644
index f08dfec..0000000
--- a/video/filter/vf_buffer.c
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-
-#include "mpv_talloc.h"
-
-#include "options/m_option.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-
-#include "vf.h"
-
-#define MAX_Q 100
-
-struct vf_priv_s {
- struct mp_image *queued[MAX_Q];
- int num_queued;
- int cfg_num;
-};
-
-static void flush(struct vf_instance *vf)
-{
- for (int n = 0; n < vf->priv->num_queued; n++)
- mp_image_unrefp(&vf->priv->queued[n]);
- vf->priv->num_queued = 0;
-}
-
-static int filter_ext(struct vf_instance *vf, struct mp_image *mpi)
-{
- struct vf_priv_s *p = vf->priv;
- if (mpi) {
- if (p->num_queued == p->cfg_num) {
- vf_add_output_frame(vf, p->queued[p->num_queued - 1]);
- p->num_queued--;
- }
- p->num_queued++;
- for (int n = p->num_queued - 1; n > 0; n--)
- p->queued[n] = p->queued[n - 1];
- p->queued[0] = mpi;
- } else {
- // EOF
- while (p->num_queued) {
- vf_add_output_frame(vf, p->queued[p->num_queued - 1]);
- p->num_queued--;
- }
- }
- return 0;
-}
-
-static int control(vf_instance_t *vf, int request, void *data)
-{
- if (request == VFCTRL_SEEK_RESET) {
- flush(vf);
- return CONTROL_OK;
- }
- return CONTROL_UNKNOWN;
-}
-
-static void uninit(vf_instance_t *vf)
-{
- flush(vf);
-}
-
-static int vf_open(vf_instance_t *vf)
-{
- MP_WARN(vf, "This filter is deprecated. No replacement.\n");
- vf->filter_ext = filter_ext;
- vf->control = control;
- vf->uninit = uninit;
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-const vf_info_t vf_info_buffer = {
- .description = "buffer a number of frames",
- .name = "buffer",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .options = (const struct m_option[]){
- OPT_INTRANGE("num", cfg_num, 0, 1, MAX_Q, OPTDEF_INT(2)),
- {0}
- },
-};
diff --git a/video/filter/vf_convert.c b/video/filter/vf_convert.c
new file mode 100644
index 0000000..0714a53
--- /dev/null
+++ b/video/filter/vf_convert.c
@@ -0,0 +1,133 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include <libswscale/swscale.h>
+
+#include "common/av_common.h"
+#include "common/msg.h"
+
+#include "options/options.h"
+
+#include "video/img_format.h"
+#include "video/mp_image.h"
+#include "video/sws_utils.h"
+#include "video/fmt-conversion.h"
+#include "vf.h"
+
+struct vf_priv_s {
+ struct mp_sws_context *sws;
+};
+
+static int find_best_out(vf_instance_t *vf, int in_format)
+{
+ int best = 0;
+ for (int out_format = IMGFMT_START; out_format < IMGFMT_END; out_format++) {
+ if (!vf_next_query_format(vf, out_format))
+ continue;
+ if (sws_isSupportedOutput(imgfmt2pixfmt(out_format)) < 1)
+ continue;
+ if (best) {
+ int candidate = mp_imgfmt_select_best(best, out_format, in_format);
+ if (candidate)
+ best = candidate;
+ } else {
+ best = out_format;
+ }
+ }
+ return best;
+}
+
+static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
+ struct mp_image_params *out)
+{
+ unsigned int best = find_best_out(vf, in->imgfmt);
+ if (!best) {
+ MP_WARN(vf, "no supported output format found\n");
+ return -1;
+ }
+
+ *out = *in;
+ out->imgfmt = best;
+
+ // If we convert from RGB to YUV, default to limited range.
+ if (mp_imgfmt_get_forced_csp(in->imgfmt) == MP_CSP_RGB &&
+ mp_imgfmt_get_forced_csp(out->imgfmt) == MP_CSP_AUTO)
+ out->color.levels = MP_CSP_LEVELS_TV;
+
+ mp_image_params_guess_csp(out);
+
+ mp_sws_set_from_cmdline(vf->priv->sws, vf->chain->opts->vo->sws_opts);
+ vf->priv->sws->src = *in;
+ vf->priv->sws->dst = *out;
+
+ if (mp_sws_reinit(vf->priv->sws) < 0) {
+ // error...
+ MP_WARN(vf, "Couldn't init libswscale for this setup\n");
+ return -1;
+ }
+ return 0;
+}
+
+static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
+{
+ struct mp_image *dmpi = vf_alloc_out_image(vf);
+ if (!dmpi)
+ return NULL;
+ mp_image_copy_attributes(dmpi, mpi);
+
+ mp_sws_scale(vf->priv->sws, dmpi, mpi);
+
+ talloc_free(mpi);
+ return dmpi;
+}
+
+static int query_format(struct vf_instance *vf, unsigned int fmt)
+{
+ if (IMGFMT_IS_HWACCEL(fmt) || sws_isSupportedInput(imgfmt2pixfmt(fmt)) < 1)
+ return 0;
+ return !!find_best_out(vf, fmt);
+}
+
+static void uninit(struct vf_instance *vf)
+{
+}
+
+static int vf_open(vf_instance_t *vf)
+{
+ vf->reconfig = reconfig;
+ vf->filter = filter;
+ vf->query_format = query_format;
+ vf->uninit = uninit;
+ vf->priv->sws = mp_sws_alloc(vf);
+ vf->priv->sws->log = vf->log;
+ return 1;
+}
+
+const vf_info_t vf_info_convert = {
+ .description = "image format conversion with libswscale",
+ .name = "convert",
+ .open = vf_open,
+ .priv_size = sizeof(struct vf_priv_s),
+};
diff --git a/video/filter/vf_crop.c b/video/filter/vf_crop.c
deleted file mode 100644
index 79a2fce..0000000
--- a/video/filter/vf_crop.c
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "config.h"
-#include "common/msg.h"
-#include "options/options.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-#include "vf.h"
-
-#include "options/m_option.h"
-
-static const struct vf_priv_s {
- int crop_w,crop_h;
- int crop_x,crop_y;
-} vf_priv_dflt = {
- -1,-1,
- -1,-1
-};
-
-//===========================================================================//
-
-static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
- struct mp_image_params *out)
-{
- int width = in->w, height = in->h;
-
- // calculate the missing parameters:
- if(vf->priv->crop_w<=0 || vf->priv->crop_w>width) vf->priv->crop_w=width;
- if(vf->priv->crop_h<=0 || vf->priv->crop_h>height) vf->priv->crop_h=height;
- if(vf->priv->crop_x<0) vf->priv->crop_x=(width-vf->priv->crop_w)/2;
- if(vf->priv->crop_y<0) vf->priv->crop_y=(height-vf->priv->crop_h)/2;
- // rounding:
-
- int orig_x = vf->priv->crop_x;
- int orig_y = vf->priv->crop_y;
-
- struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(in->imgfmt);
-
- if (fmt.flags & MP_IMGFLAG_HWACCEL) {
- vf->priv->crop_x = 0;
- vf->priv->crop_y = 0;
- } else {
- vf->priv->crop_x = MP_ALIGN_DOWN(vf->priv->crop_x, fmt.align_x);
- vf->priv->crop_y = MP_ALIGN_DOWN(vf->priv->crop_y, fmt.align_y);
- }
-
- if (vf->priv->crop_x != orig_x || vf->priv->crop_y != orig_y) {
- MP_WARN(vf, "Adjusting crop origin to %d/%d for pixel format alignment.\n",
- vf->priv->crop_x, vf->priv->crop_y);
- }
-
- // check:
- if(vf->priv->crop_w+vf->priv->crop_x>width ||
- vf->priv->crop_h+vf->priv->crop_y>height){
- MP_WARN(vf, "Bad position/width/height - cropped area outside of the original!\n");
- return -1;
- }
-
- *out = *in;
- out->w = vf->priv->crop_w;
- out->h = vf->priv->crop_h;
- return 0;
-}
-
-static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
-{
- if (mpi->fmt.flags & MP_IMGFLAG_HWACCEL) {
- mp_image_set_size(mpi, vf->fmt_out.w, vf->fmt_out.h);
- } else {
- mp_image_crop(mpi, vf->priv->crop_x, vf->priv->crop_y,
- vf->priv->crop_x + vf->priv->crop_w,
- vf->priv->crop_y + vf->priv->crop_h);
- }
- return mpi;
-}
-
-static int query_format(struct vf_instance *vf, unsigned int fmt)
-{
- return vf_next_query_format(vf, fmt);
-}
-
-static int vf_open(vf_instance_t *vf){
- MP_WARN(vf, "This filter is deprecated. Use lavfi crop instead.\n");
- vf->reconfig=reconfig;
- vf->filter=filter;
- vf->query_format=query_format;
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_INT("w", crop_w, M_OPT_MIN, .min = 0),
- OPT_INT("h", crop_h, M_OPT_MIN, .min = 0),
- OPT_INT("x", crop_x, M_OPT_MIN, .min = -1),
- OPT_INT("y", crop_y, M_OPT_MIN, .min = -1),
- {0}
-};
-
-const vf_info_t vf_info_crop = {
- .description = "cropping",
- .name = "crop",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_dflt,
- .options = vf_opts_fields,
-};
-
-//===========================================================================//
diff --git a/video/filter/vf_d3d11vpp.c b/video/filter/vf_d3d11vpp.c
index 44178a8..f49af06 100644
--- a/video/filter/vf_d3d11vpp.c
+++ b/video/filter/vf_d3d11vpp.c
@@ -19,6 +19,9 @@
#include <windows.h>
#include <d3d11.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_d3d11va.h>
+
#include "common/common.h"
#include "osdep/timer.h"
#include "osdep/windows_utils.h"
@@ -402,6 +405,7 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
p->out_shared = true;
p->out_rgb = true;
}
+ out->hw_flags = 0;
p->require_filtering = in->hw_subfmt != out->hw_subfmt;
@@ -476,6 +480,9 @@ static int vf_open(vf_instance_t *vf)
{
struct vf_priv_s *p = vf->priv;
+ if (!vf->hwdec_devs)
+ return 0;
+
vf->reconfig = reconfig;
vf->filter_ext = filter_ext;
vf->filter_out = filter_out;
@@ -483,14 +490,22 @@ static int vf_open(vf_instance_t *vf)
vf->uninit = uninit;
vf->control = control;
- p->queue = mp_refqueue_alloc();
-
- p->vo_dev = hwdec_devices_load(vf->hwdec_devs, HWDEC_D3D11VA);
- if (!p->vo_dev)
+ hwdec_devices_request_all(vf->hwdec_devs);
+ AVBufferRef *ref =
+ hwdec_devices_get_lavc(vf->hwdec_devs, AV_HWDEVICE_TYPE_D3D11VA);
+ if (!ref)
return 0;
+ AVHWDeviceContext *hwctx = (void *)ref->data;
+ AVD3D11VADeviceContext *d3dctx = hwctx->hwctx;
+
+ p->vo_dev = d3dctx->device;
ID3D11Device_AddRef(p->vo_dev);
+ av_buffer_unref(&ref);
+
+ p->queue = mp_refqueue_alloc();
+
HRESULT hr;
hr = ID3D11Device_QueryInterface(p->vo_dev, &IID_ID3D11VideoDevice,
diff --git a/video/filter/vf_dsize.c b/video/filter/vf_dsize.c
deleted file mode 100644
index 27d21c0..0000000
--- a/video/filter/vf_dsize.c
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-#include <limits.h>
-
-#include "config.h"
-#include "common/msg.h"
-#include "options/m_option.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-#include "vf.h"
-
-struct vf_priv_s {
- int w, h;
- int method; // aspect method, 0 -> downscale, 1-> upscale. +2 -> original aspect.
- int round;
- float aspect;
-};
-
-static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
- struct mp_image_params *out)
-{
- int width = in->w, height = in->h;
- int d_width, d_height;
- mp_image_params_get_dsize(in, &d_width, &d_height);
- int w = vf->priv->w;
- int h = vf->priv->h;
- if (vf->priv->aspect < 0.001) { // did the user input aspect or w,h params
- if (w == 0) w = d_width;
- if (h == 0) h = d_height;
- if (w == -1) w = width;
- if (h == -1) h = height;
- if (w == -2) w = h * (double)d_width / d_height;
- if (w == -3) w = h * (double)width / height;
- if (h == -2) h = w * (double)d_height / d_width;
- if (h == -3) h = w * (double)height / width;
- if (vf->priv->method > -1) {
- double aspect = (vf->priv->method & 2) ? ((double)height / width) : ((double)d_height / d_width);
- if ((h > w * aspect) ^ (vf->priv->method & 1)) {
- h = w * aspect;
- } else {
- w = h / aspect;
- }
- }
- if (vf->priv->round > 1) { // round up
- w += (vf->priv->round - 1 - (w - 1) % vf->priv->round);
- h += (vf->priv->round - 1 - (h - 1) % vf->priv->round);
- }
- d_width = w;
- d_height = h;
- } else {
- if (vf->priv->aspect * height > width) {
- d_width = height * vf->priv->aspect + .5;
- d_height = height;
- } else {
- d_height = width / vf->priv->aspect + .5;
- d_width = width;
- }
- }
- *out = *in;
- mp_image_params_set_dsize(out, d_width, d_height);
- return 0;
-}
-
-static int vf_open(vf_instance_t *vf)
-{
- MP_WARN(vf, "This filter is deprecated. No replacement.\n");
-
- vf->reconfig = reconfig;
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-const vf_info_t vf_info_dsize = {
- .description = "reset displaysize/aspect",
- .name = "dsize",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &(const struct vf_priv_s){
- .aspect = 0.0,
- .w = -1,
- .h = -1,
- .method = -1,
- .round = 1,
- },
- .options = (const struct m_option[]){
- OPT_INTRANGE("w", w, 0, -3, INT_MAX),
- OPT_INTRANGE("h", h, 0, -3, INT_MAX),
- OPT_INTRANGE("method", method, 0, -1, 3),
- OPT_INTRANGE("round", round, 0, 0, 9999),
- OPT_FLOAT("aspect", aspect, CONF_RANGE, .min = 0, .max = 10),
- {0}
- },
-};
diff --git a/video/filter/vf_expand.c b/video/filter/vf_expand.c
deleted file mode 100644
index 216c47a..0000000
--- a/video/filter/vf_expand.c
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-
-#include <libavutil/common.h>
-
-#include "config.h"
-#include "common/msg.h"
-#include "options/options.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-#include "vf.h"
-
-#include "options/m_option.h"
-
-static struct vf_priv_s {
- // These four values are a backup of the values parsed from the command line.
- // This is necessary so that we do not get a mess upon filter reinit due to
- // e.g. aspect changes and with only aspect specified on the command line,
- // where we would otherwise use the values calculated for a different aspect
- // instead of recalculating them again.
- int cfg_exp_w, cfg_exp_h;
- int cfg_exp_x, cfg_exp_y;
- int exp_w,exp_h;
- int exp_x,exp_y;
- double aspect;
- int round;
-} const vf_priv_dflt = {
- -1,-1,
- -1,-1,
- -1,-1,
- -1,-1,
- 0.,
- 1,
-};
-
-//===========================================================================//
-
-static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
- struct mp_image_params *out)
-{
- int width = in->w, height = in->h;
-
- vf->priv->exp_x = vf->priv->cfg_exp_x;
- vf->priv->exp_y = vf->priv->cfg_exp_y;
- vf->priv->exp_w = vf->priv->cfg_exp_w;
- vf->priv->exp_h = vf->priv->cfg_exp_h;
- if ( vf->priv->exp_w == -1 ) vf->priv->exp_w=width;
- else if (vf->priv->exp_w < -1 ) vf->priv->exp_w=width - vf->priv->exp_w;
- else if ( vf->priv->exp_w<width ) vf->priv->exp_w=width;
- if ( vf->priv->exp_h == -1 ) vf->priv->exp_h=height;
- else if ( vf->priv->exp_h < -1 ) vf->priv->exp_h=height - vf->priv->exp_h;
- else if( vf->priv->exp_h<height ) vf->priv->exp_h=height;
- if (vf->priv->aspect) {
- float adjusted_aspect = vf->priv->aspect;
- adjusted_aspect *= (double)in->p_w/in->p_h;
- if (vf->priv->exp_h < vf->priv->exp_w / adjusted_aspect) {
- vf->priv->exp_h = vf->priv->exp_w / adjusted_aspect + 0.5;
- } else {
- vf->priv->exp_w = vf->priv->exp_h * adjusted_aspect + 0.5;
- }
- }
- if (vf->priv->round > 1) { // round up.
- vf->priv->exp_w = (1 + (vf->priv->exp_w - 1) / vf->priv->round) * vf->priv->round;
- vf->priv->exp_h = (1 + (vf->priv->exp_h - 1) / vf->priv->round) * vf->priv->round;
- }
-
- if(vf->priv->exp_x<0 || vf->priv->exp_x+width>vf->priv->exp_w) vf->priv->exp_x=(vf->priv->exp_w-width)/2;
- if(vf->priv->exp_y<0 || vf->priv->exp_y+height>vf->priv->exp_h) vf->priv->exp_y=(vf->priv->exp_h-height)/2;
-
- struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(in->imgfmt);
-
- vf->priv->exp_x = MP_ALIGN_DOWN(vf->priv->exp_x, fmt.align_x);
- vf->priv->exp_y = MP_ALIGN_DOWN(vf->priv->exp_y, fmt.align_y);
-
- *out = *in;
- out->w = vf->priv->exp_w;
- out->h = vf->priv->exp_h;
-
- return 0;
-}
-
-static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
-{
- int e_x = vf->priv->exp_x, e_y = vf->priv->exp_y;
- int e_w = vf->priv->exp_w, e_h = vf->priv->exp_h;
-
- if (e_x == 0 && e_y == 0 && e_w == mpi->w && e_h == mpi->h)
- return mpi;
-
- struct mp_image *dmpi = vf_alloc_out_image(vf);
- if (!dmpi) {
- talloc_free(mpi);
- return NULL;
- }
- mp_image_copy_attributes(dmpi, mpi);
-
- struct mp_image cropped = *dmpi;
- mp_image_crop(&cropped, e_x, e_y, e_x + mpi->w, e_y + mpi->h);
- mp_image_copy(&cropped, mpi);
-
- int e_x2 = e_x + MP_ALIGN_DOWN(mpi->w, mpi->fmt.align_x);
- int e_y2 = e_y + MP_ALIGN_DOWN(mpi->h, mpi->fmt.align_y);
-
- // top border (over the full width)
- mp_image_clear(dmpi, 0, 0, e_w, e_y);
- // bottom border (over the full width)
- mp_image_clear(dmpi, 0, e_y2, e_w, e_h);
- // left
- mp_image_clear(dmpi, 0, e_y, e_x, e_y2);
- // right
- mp_image_clear(dmpi, e_x2, e_y, e_w, e_y2);
-
- talloc_free(mpi);
- return dmpi;
-}
-
-static int query_format(struct vf_instance *vf, unsigned int fmt)
-{
- if (!IMGFMT_IS_HWACCEL(fmt))
- return vf_next_query_format(vf, fmt);
- return 0;
-}
-
-static int vf_open(vf_instance_t *vf){
- MP_WARN(vf, "This filter is deprecated. Use lavfi pad instead.\n");
-
- vf->reconfig=reconfig;
- vf->query_format=query_format;
- vf->filter=filter;
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_INT("w", cfg_exp_w, 0),
- OPT_INT("h", cfg_exp_h, 0),
- OPT_INT("x", cfg_exp_x, M_OPT_MIN, .min = -1),
- OPT_INT("y", cfg_exp_y, M_OPT_MIN, .min = -1),
- OPT_DOUBLE("aspect", aspect, M_OPT_MIN, .min = 0),
- OPT_INT("round", round, M_OPT_MIN, .min = 1),
- {0}
-};
-
-const vf_info_t vf_info_expand = {
- .description = "expanding",
- .name = "expand",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_dflt,
- .options = vf_opts_fields,
-};
-
-//===========================================================================//
diff --git a/video/filter/vf_flip.c b/video/filter/vf_flip.c
deleted file mode 100644
index 30658e4..0000000
--- a/video/filter/vf_flip.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "config.h"
-#include "common/msg.h"
-
-#include "video/mp_image.h"
-#include "vf.h"
-
-#include "video/out/vo.h"
-
-static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
-{
- mp_image_vflip(mpi);
- return mpi;
-}
-
-static int query_format(struct vf_instance *vf, unsigned int fmt)
-{
- if (!IMGFMT_IS_HWACCEL(fmt))
- return vf_next_query_format(vf, fmt);
- return 0;
-}
-
-static int vf_open(vf_instance_t *vf){
- MP_WARN(vf, "This filter is deprecated. Use lavfi vflip instead.\n");
-
- vf->filter=filter;
- vf->query_format = query_format;
- return 1;
-}
-
-const vf_info_t vf_info_flip = {
- .description = "flip image upside-down",
- .name = "flip",
- .open = vf_open,
-};
diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c
index 581bbe3..aab3085 100644
--- a/video/filter/vf_format.c
+++ b/video/filter/vf_format.c
@@ -1,18 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
diff --git a/video/filter/vf_gradfun.c b/video/filter/vf_gradfun.c
deleted file mode 100644
index c34b77f..0000000
--- a/video/filter/vf_gradfun.c
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2009 Loren Merritt <lorenm@u.washignton.edu>
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <inttypes.h>
-#include <math.h>
-
-#include "vf.h"
-
-#include "common/common.h"
-#include "options/m_option.h"
-
-#include "vf_lavfi.h"
-
-struct vf_priv_s {
- float cfg_thresh;
- int cfg_radius;
- float cfg_size;
- int radius;
-
- struct vf_lw_opts *lw_opts;
-} const vf_priv_dflt = {
- .cfg_thresh = 1.5,
- .cfg_radius = -1,
- .cfg_size = -1,
-};
-
-static int lavfi_reconfig(struct vf_instance *vf,
- struct mp_image_params *in,
- struct mp_image_params *out)
-{
- struct vf_priv_s *p = vf_lw_old_priv(vf);
- int w = in->w;
- int h = in->h;
- p->radius = p->cfg_radius;
- if (p->cfg_size > -1)
- p->radius = (p->cfg_size / 100.0f) * sqrtf(w * w + h * h);
- p->radius = MPCLAMP((p->radius+1)&~1, 4, 32);
- vf_lw_update_graph(vf, "gradfun", "%f:%d", p->cfg_thresh, p->radius);
- return 0;
-}
-
-static int vf_open(vf_instance_t *vf)
-{
- MP_WARN(vf, "%s", VF_LW_REPLACE);
-
- bool have_radius = vf->priv->cfg_radius > -1;
- bool have_size = vf->priv->cfg_size > -1;
-
- if (have_radius && have_size) {
- MP_ERR(vf, "scale: gradfun: only one of "
- "radius/size parameters allowed at the same time!\n");
- return 0;
- }
-
- if (!have_radius && !have_size)
- vf->priv->cfg_size = 1.0;
-
- if (vf_lw_set_graph(vf, vf->priv->lw_opts, "gradfun", "%f:4",
- vf->priv->cfg_thresh) >= 0)
- {
- vf_lw_set_reconfig_cb(vf, lavfi_reconfig);
- return 1;
- }
-
- MP_FATAL(vf, "This version of libavfilter has no 'gradfun' filter.\n");
- return 0;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_FLOATRANGE("strength", cfg_thresh, 0, 0.51, 255),
- OPT_INTRANGE("radius", cfg_radius, 0, 4, 32),
- OPT_FLOATRANGE("size", cfg_size, 0, 0.1, 5.0),
- OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
- {0}
-};
-
-const vf_info_t vf_info_gradfun = {
- .description = "gradient deband",
- .name = "gradfun",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_dflt,
- .options = vf_opts_fields,
-};
diff --git a/video/filter/vf_lavfi.c b/video/filter/vf_lavfi.c
index eefa051..0cd3af8 100644
--- a/video/filter/vf_lavfi.c
+++ b/video/filter/vf_lavfi.c
@@ -50,7 +50,6 @@
#include "video/sws_utils.h"
#include "video/fmt-conversion.h"
#include "vf.h"
-#include "vf_lavfi.h"
// FFmpeg and Libav have slightly different APIs, just enough to cause us
// unnecessary pain. <Expletive deleted.>
@@ -326,7 +325,7 @@ static void get_metadata_from_av_frame(struct vf_instance *vf, AVFrame *frame)
if (!p->metadata)
p->metadata = talloc_zero(p, struct mp_tags);
- mp_tags_copy_from_av_dictionary(p->metadata, av_frame_get_metadata(frame));
+ mp_tags_copy_from_av_dictionary(p->metadata, frame->metadata);
#endif
}
@@ -516,94 +515,3 @@ const vf_info_t vf_info_lavfi_bridge = {
},
.print_help = print_help,
};
-
-// The following code is for the old filters wrapper code.
-
-struct vf_lw_opts {
- int64_t sws_flags;
- char **avopts;
-};
-
-#undef OPT_BASE_STRUCT
-#define OPT_BASE_STRUCT struct vf_lw_opts
-const struct m_sub_options vf_lw_conf = {
- .opts = (const m_option_t[]) {
- OPT_INT64("lavfi-sws-flags", sws_flags, 0),
- OPT_KEYVALUELIST("lavfi-o", avopts, 0),
- {0}
- },
- .defaults = &(const struct vf_lw_opts){
- .sws_flags = SWS_BICUBIC,
- },
- .size = sizeof(struct vf_lw_opts),
-};
-
-static bool have_filter(const char *name)
-{
- for (const AVFilter *filter = avfilter_next(NULL); filter;
- filter = avfilter_next(filter))
- {
- if (strcmp(filter->name, name) == 0)
- return true;
- }
- return false;
-}
-
-// This is used by "old" filters for wrapping lavfi if possible.
-// On success, this overwrites all vf callbacks and literally takes over the
-// old filter and replaces it with vf_lavfi.
-// On error (<0), nothing is changed.
-int vf_lw_set_graph(struct vf_instance *vf, struct vf_lw_opts *lavfi_opts,
- char *filter, char *opts, ...)
-{
- if (!lavfi_opts)
- lavfi_opts = (struct vf_lw_opts *)vf_lw_conf.defaults;
- if (filter && !have_filter(filter))
- return -1;
- MP_VERBOSE(vf, "Using libavfilter for '%s'\n", vf->info->name);
- void *old_priv = vf->priv;
- struct vf_priv_s *p = talloc(vf, struct vf_priv_s);
- vf->priv = p;
- *p = *(const struct vf_priv_s *)vf_info_lavfi.priv_defaults;
- p->cfg_sws_flags = lavfi_opts->sws_flags;
- p->cfg_avopts = lavfi_opts->avopts;
- va_list ap;
- va_start(ap, opts);
- char *s = talloc_vasprintf(vf, opts, ap);
- p->cfg_graph = filter ? talloc_asprintf(vf, "%s=%s", filter, s)
- : talloc_strdup(vf, s);
- talloc_free(s);
- va_end(ap);
- p->old_priv = old_priv;
- // Note: we should be sure vf_open really overwrites _all_ vf callbacks.
- if (vf_open(vf) < 1)
- abort();
- return 1;
-}
-
-void *vf_lw_old_priv(struct vf_instance *vf)
-{
- struct vf_priv_s *p = vf->priv;
- return p->old_priv;
-}
-
-void vf_lw_update_graph(struct vf_instance *vf, char *filter, char *opts, ...)
-{
- struct vf_priv_s *p = vf->priv;
- va_list ap;
- va_start(ap, opts);
- char *s = talloc_vasprintf(vf, opts, ap);
- talloc_free(p->cfg_graph);
- p->cfg_graph = filter ? talloc_asprintf(vf, "%s=%s", filter, s)
- : talloc_strdup(vf, s);
- talloc_free(s);
- va_end(ap);
-}
-
-void vf_lw_set_reconfig_cb(struct vf_instance *vf,
- int (*reconfig_)(struct vf_instance *vf,
- struct mp_image_params *in,
- struct mp_image_params *out))
-{
- vf->priv->lw_reconfig_cb = reconfig_;
-}
diff --git a/video/filter/vf_lavfi.h b/video/filter/vf_lavfi.h
deleted file mode 100644
index 9196532..0000000
--- a/video/filter/vf_lavfi.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef MP_VF_LAVFI_H_
-#define MP_VF_LAVFI_H_
-
-#include "common/common.h"
-#include "vf.h"
-
-struct vf_lw_opts;
-
-extern const struct m_sub_options vf_lw_conf;
-
-int vf_lw_set_graph(struct vf_instance *vf, struct vf_lw_opts *lavfi_opts,
- char *filter, char *opts, ...) PRINTF_ATTRIBUTE(4,5);
-void *vf_lw_old_priv(struct vf_instance *vf);
-void vf_lw_update_graph(struct vf_instance *vf, char *filter, char *opts, ...)
- PRINTF_ATTRIBUTE(3,4);
-void vf_lw_set_reconfig_cb(struct vf_instance *vf,
- int (*reconfig)(struct vf_instance *vf,
- struct mp_image_params *in,
- struct mp_image_params *out));
-
-#define VF_LW_REPLACE "This filter will be replaced by using libavfilter " \
- "option syntax directly. Parts of the old syntax will stop working, " \
- "and some defaults may change.\n"
-
-#endif
diff --git a/video/filter/vf_mirror.c b/video/filter/vf_mirror.c
deleted file mode 100644
index f835d38..0000000
--- a/video/filter/vf_mirror.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stddef.h>
-
-#include "vf.h"
-#include "vf_lavfi.h"
-
-static int vf_open(vf_instance_t *vf)
-{
- MP_WARN(vf, "This filter is deprecated. Use lavfi hflip instead.\n");
- return vf_lw_set_graph(vf, NULL, NULL, "hflip") >= 0;
-}
-
-const vf_info_t vf_info_mirror = {
- .description = "horizontal mirror",
- .name = "mirror",
- .open = vf_open,
-};
diff --git a/video/filter/vf_noformat.c b/video/filter/vf_noformat.c
deleted file mode 100644
index 2d3985c..0000000
--- a/video/filter/vf_noformat.c
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-
-#include "config.h"
-#include "common/msg.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-#include "vf.h"
-
-#include "options/m_option.h"
-
-static struct vf_priv_s {
- int fmt;
-} const vf_priv_dflt = {
- IMGFMT_420P
-};
-
-//===========================================================================//
-
-static int query_format(struct vf_instance *vf, unsigned int fmt){
- if(fmt!=vf->priv->fmt)
- return vf_next_query_format(vf,fmt);
- return 0;
-}
-
-static int vf_open(vf_instance_t *vf){
- MP_WARN(vf, "This filter is deprecated and will be removed (no replacement)\n");
- vf->query_format=query_format;
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_IMAGEFORMAT("fmt", fmt, 0),
- {0}
-};
-
-const vf_info_t vf_info_noformat = {
- .description = "disallow one output format",
- .name = "noformat",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_dflt,
- .options = vf_opts_fields,
-};
-
-//===========================================================================//
diff --git a/video/filter/vf_pullup.c b/video/filter/vf_pullup.c
deleted file mode 100644
index eee6f43..0000000
--- a/video/filter/vf_pullup.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "common/msg.h"
-#include "options/m_option.h"
-
-#include "vf.h"
-
-#include "vf_lavfi.h"
-
-struct vf_priv_s {
- struct pullup_context *ctx;
- int init;
- int fakecount;
- double lastpts;
- int junk_left, junk_right, junk_top, junk_bottom;
- int strict_breaks, metric_plane;
- struct vf_lw_opts *lw_opts;
-};
-
-static int vf_open(vf_instance_t *vf)
-{
- MP_WARN(vf, "%s", VF_LW_REPLACE);
-
- struct vf_priv_s *p = vf->priv;
- const char *pname[3] = {"y", "u", "v"};
- if (vf_lw_set_graph(vf, p->lw_opts, "pullup", "%d:%d:%d:%d:%d:%s",
- p->junk_left, p->junk_right, p->junk_top, p->junk_bottom,
- p->strict_breaks, pname[p->metric_plane]) >= 0)
- {
- return 1;
- }
-
- MP_FATAL(vf, "This version of libavfilter has no 'pullup' filter.\n");
- return 0;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-const vf_info_t vf_info_pullup = {
- .description = "pullup (from field sequence to frames)",
- .name = "pullup",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &(const struct vf_priv_s){
- .junk_left = 1,
- .junk_right = 1,
- .junk_top = 4,
- .junk_bottom = 4,
- },
- .options = (const struct m_option[]){
- OPT_INT("jl", junk_left, 0),
- OPT_INT("jr", junk_right, 0),
- OPT_INT("jt", junk_top, 0),
- OPT_INT("jb", junk_bottom, 0),
- OPT_INT("sb", strict_breaks, 0),
- OPT_CHOICE("mp", metric_plane, 0, ({"y", 0}, {"u", 1}, {"v", 2})),
- OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
- {0}
- },
-};
diff --git a/video/filter/vf_rotate.c b/video/filter/vf_rotate.c
deleted file mode 100644
index be1247f..0000000
--- a/video/filter/vf_rotate.c
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-#include <inttypes.h>
-
-#include "common/msg.h"
-#include "options/m_option.h"
-
-#include "vf.h"
-#include "vf_lavfi.h"
-
-struct vf_priv_s {
- int angle;
- int warn;
- struct vf_lw_opts *lw_opts;
-};
-
-static const char *const rot[] = {
- "null",
- "transpose=clock",
- "vflip,hflip",
- "transpose=cclock",
- "null", // actually set in lavfi_recreate()
-};
-
-static int lavfi_reconfig(struct vf_instance *vf,
- struct mp_image_params *in,
- struct mp_image_params *out)
-{
- struct vf_priv_s *p = vf_lw_old_priv(vf);
- if (p->angle == 4) { // "auto"
- int r = in->rotate;
- if (r < 0 || r >= 360) {
- MP_ERR(vf, "Can't apply rotation of %d degrees.\n", r);
- return -1;
- }
- if (r % 90) {
- double a = r / 180.0 * M_PI;
- vf_lw_update_graph(vf, NULL, "rotate=%f:ow=rotw(%f):oh=roth(%f)",
- a, a, a);
- } else {
- vf_lw_update_graph(vf, NULL, "%s", rot[(r / 90) % 360]);
- }
- out->rotate = 0;
- }
- return 0;
-}
-
-static int vf_open(vf_instance_t *vf)
-{
- struct vf_priv_s *p = vf->priv;
-
- if (p->warn)
- MP_WARN(vf, "%s", VF_LW_REPLACE);
-
- if (vf_lw_set_graph(vf, p->lw_opts, NULL, "%s", rot[p->angle]) >= 0) {
- vf_lw_set_reconfig_cb(vf, lavfi_reconfig);
- return 1;
- }
-
- return 0;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-const vf_info_t vf_info_rotate = {
- .description = "rotate",
- .name = "rotate",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .options = (const struct m_option[]){
- OPT_CHOICE("angle", angle, 0,
- ({"0", 0},
- {"90", 1},
- {"180", 2},
- {"270", 3},
- {"auto", 4})),
- OPT_FLAG("warn", warn, 0, OPTDEF_INT(1)),
- OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
- {0}
- },
-};
-
-//===========================================================================//
diff --git a/video/filter/vf_scale.c b/video/filter/vf_scale.c
deleted file mode 100644
index 9c74db3..0000000
--- a/video/filter/vf_scale.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-#include <sys/types.h>
-
-#include <libswscale/swscale.h>
-
-#include "config.h"
-#include "common/msg.h"
-#include "options/options.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-#include "vf.h"
-#include "video/fmt-conversion.h"
-
-#include "video/sws_utils.h"
-
-#include "video/csputils.h"
-#include "video/out/vo.h"
-
-#include "options/m_option.h"
-
-#include "vf_lavfi.h"
-
-static struct vf_priv_s {
- int w, h;
- int cfg_w, cfg_h;
- int v_chr_drop;
- double param[2];
- struct mp_sws_context *sws;
- int noup;
- int accurate_rnd;
- int warn;
-} const vf_priv_dflt = {
- 0, 0,
- -1, -1,
- 0,
- {SWS_PARAM_DEFAULT, SWS_PARAM_DEFAULT},
- .warn = 1,
-};
-
-static int find_best_out(vf_instance_t *vf, int in_format)
-{
- int best = 0;
- for (int out_format = IMGFMT_START; out_format < IMGFMT_END; out_format++) {
- if (!vf_next_query_format(vf, out_format))
- continue;
- if (sws_isSupportedOutput(imgfmt2pixfmt(out_format)) < 1)
- continue;
- if (best) {
- int candidate = mp_imgfmt_select_best(best, out_format, in_format);
- if (candidate)
- best = candidate;
- } else {
- best = out_format;
- }
- }
- return best;
-}
-
-static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
- struct mp_image_params *out)
-{
- int width = in->w, height = in->h;
- int d_width, d_height;
- mp_image_params_get_dsize(in, &d_width, &d_height);
-
- unsigned int best = find_best_out(vf, in->imgfmt);
- int round_w = 0, round_h = 0;
-
- if (!best) {
- MP_WARN(vf, "no supported output format found\n");
- return -1;
- }
-
- vf->next->query_format(vf->next, best);
-
- vf->priv->w = vf->priv->cfg_w;
- vf->priv->h = vf->priv->cfg_h;
-
- if (vf->priv->w <= -8) {
- vf->priv->w += 8;
- round_w = 1;
- }
- if (vf->priv->h <= -8) {
- vf->priv->h += 8;
- round_h = 1;
- }
-
- if (vf->priv->w < -3 || vf->priv->h < -3 ||
- (vf->priv->w < -1 && vf->priv->h < -1))
- {
- MP_ERR(vf, "invalid parameters\n");
- return -1;
- }
-
- if (vf->priv->w == -1)
- vf->priv->w = width;
- if (vf->priv->w == 0)
- vf->priv->w = d_width;
-
- if (vf->priv->h == -1)
- vf->priv->h = height;
- if (vf->priv->h == 0)
- vf->priv->h = d_height;
-
- if (vf->priv->w == -3)
- vf->priv->w = vf->priv->h * width / height;
- if (vf->priv->w == -2)
- vf->priv->w = vf->priv->h * d_width / d_height;
-
- if (vf->priv->h == -3)
- vf->priv->h = vf->priv->w * height / width;
- if (vf->priv->h == -2)
- vf->priv->h = vf->priv->w * d_height / d_width;
-
- if (round_w)
- vf->priv->w = ((vf->priv->w + 8) / 16) * 16;
- if (round_h)
- vf->priv->h = ((vf->priv->h + 8) / 16) * 16;
-
- // check for upscaling, now that all parameters had been applied
- if (vf->priv->noup) {
- if ((vf->priv->w > width) + (vf->priv->h > height) >= vf->priv->noup) {
- vf->priv->w = width;
- vf->priv->h = height;
- }
- }
-
- MP_DBG(vf, "scaling %dx%d to %dx%d\n", width, height, vf->priv->w, vf->priv->h);
-
- // Compute new d_width and d_height, preserving aspect
- // while ensuring that both are >= output size in pixels.
- if (vf->priv->h * d_width > vf->priv->w * d_height) {
- d_width = vf->priv->h * d_width / d_height;
- d_height = vf->priv->h;
- } else {
- d_height = vf->priv->w * d_height / d_width;
- d_width = vf->priv->w;
- }
-
- *out = *in;
- out->w = vf->priv->w;
- out->h = vf->priv->h;
- mp_image_params_set_dsize(out, d_width, d_height);
- out->imgfmt = best;
-
- // Second-guess what libswscale is going to output and what not.
- // It depends what libswscale supports for in/output, and what makes sense.
- struct mp_imgfmt_desc s_fmt = mp_imgfmt_get_desc(in->imgfmt);
- struct mp_imgfmt_desc d_fmt = mp_imgfmt_get_desc(out->imgfmt);
- // keep colorspace settings if the data stays in yuv
- if (!(s_fmt.flags & MP_IMGFLAG_YUV) || !(d_fmt.flags & MP_IMGFLAG_YUV)) {
- out->color.space = MP_CSP_AUTO;
- out->color.levels = MP_CSP_LEVELS_AUTO;
- }
- mp_image_params_guess_csp(out);
-
- mp_sws_set_from_cmdline(vf->priv->sws, vf->chain->opts->vo->sws_opts);
- vf->priv->sws->flags |= vf->priv->v_chr_drop << SWS_SRC_V_CHR_DROP_SHIFT;
- vf->priv->sws->flags |= vf->priv->accurate_rnd * SWS_ACCURATE_RND;
- vf->priv->sws->src = *in;
- vf->priv->sws->dst = *out;
-
- if (mp_sws_reinit(vf->priv->sws) < 0) {
- // error...
- MP_WARN(vf, "Couldn't init libswscale for this setup\n");
- return -1;
- }
- return 0;
-}
-
-static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
-{
- struct mp_image *dmpi = vf_alloc_out_image(vf);
- if (!dmpi)
- return NULL;
- mp_image_copy_attributes(dmpi, mpi);
-
- mp_sws_scale(vf->priv->sws, dmpi, mpi);
-
- talloc_free(mpi);
- return dmpi;
-}
-
-static int query_format(struct vf_instance *vf, unsigned int fmt)
-{
- if (IMGFMT_IS_HWACCEL(fmt) || sws_isSupportedInput(imgfmt2pixfmt(fmt)) < 1)
- return 0;
- return !!find_best_out(vf, fmt);
-}
-
-static void uninit(struct vf_instance *vf)
-{
-}
-
-static int vf_open(vf_instance_t *vf)
-{
- vf->reconfig = reconfig;
- vf->filter = filter;
- vf->query_format = query_format;
- vf->uninit = uninit;
- vf->priv->sws = mp_sws_alloc(vf);
- vf->priv->sws->log = vf->log;
- vf->priv->sws->params[0] = vf->priv->param[0];
- vf->priv->sws->params[1] = vf->priv->param[1];
- if (vf->priv->warn)
- MP_WARN(vf, "%s", VF_LW_REPLACE);
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_INT("w", cfg_w, M_OPT_MIN, .min = -11),
- OPT_INT("h", cfg_h, M_OPT_MIN, .min = -11),
- OPT_DOUBLE("param", param[0], M_OPT_RANGE, .min = 0.0, .max = 100.0),
- OPT_DOUBLE("param2", param[1], M_OPT_RANGE, .min = 0.0, .max = 100.0),
- OPT_INTRANGE("chr-drop", v_chr_drop, 0, 0, 3),
- OPT_INTRANGE("noup", noup, 0, 0, 2),
- OPT_FLAG("arnd", accurate_rnd, 0),
- OPT_FLAG("warn", warn, 0),
- {0}
-};
-
-const vf_info_t vf_info_scale = {
- .description = "software scaling",
- .name = "scale",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_dflt,
- .options = vf_opts_fields,
-};
-
-//===========================================================================//
diff --git a/video/filter/vf_stereo3d.c b/video/filter/vf_stereo3d.c
deleted file mode 100644
index 51cfff8..0000000
--- a/video/filter/vf_stereo3d.c
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2010 Gordon Schmidt <gordon.schmidt <at> s2000.tu-chemnitz.de>
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-//==includes==//
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-
-#include <libavutil/common.h>
-
-#include "config.h"
-#include "common/msg.h"
-#include "options/options.h"
-
-#include "video/img_format.h"
-#include "video/mp_image.h"
-#include "vf.h"
-#include "options/m_option.h"
-
-#include "vf_lavfi.h"
-
-//==types==//
-typedef enum stereo_code {
- ANAGLYPH_RC_GRAY, //anaglyph red/cyan gray
- ANAGLYPH_RC_HALF, //anaglyph red/cyan half colored
- ANAGLYPH_RC_COLOR, //anaglyph red/cyan colored
- ANAGLYPH_RC_DUBOIS, //anaglyph red/cyan dubois
- ANAGLYPH_GM_GRAY, //anaglyph green/magenta gray
- ANAGLYPH_GM_HALF, //anaglyph green/magenta half colored
- ANAGLYPH_GM_COLOR, //anaglyph green/magenta colored
- ANAGLYPH_GM_DUBOIS, //anaglyph green/magenta dubois
- ANAGLYPH_YB_GRAY, //anaglyph yellow/blue gray
- ANAGLYPH_YB_HALF, //anaglyph yellow/blue half colored
- ANAGLYPH_YB_COLOR, //anaglyph yellow/blue colored
- ANAGLYPH_YB_DUBOIS, //anaglyph yellow/blue dubois
- MONO_L, //mono output for debugging (left eye only)
- MONO_R, //mono output for debugging (right eye only)
- SIDE_BY_SIDE_LR, //side by side parallel (left eye left, right eye right)
- SIDE_BY_SIDE_RL, //side by side crosseye (right eye left, left eye right)
- SIDE_BY_SIDE_2_LR, //side by side parallel with half width resolution
- SIDE_BY_SIDE_2_RL, //side by side crosseye with half width resolution
- ABOVE_BELOW_LR, //above-below (left eye above, right eye below)
- ABOVE_BELOW_RL, //above-below (right eye above, left eye below)
- ABOVE_BELOW_2_LR, //above-below with half height resolution
- ABOVE_BELOW_2_RL, //above-below with half height resolution
- INTERLEAVE_ROWS_LR, //row-interleave (left eye has top row)
- INTERLEAVE_ROWS_RL, //row-interleave (right eye has top row)
- STEREO_AUTO, //use video metadata info (for input)
- ALTERNATING_LR, //alternating frames (left first)
- ALTERNATING_RL, //alternating frames (right first)
- STEREO_CODE_COUNT //no value set - TODO: needs autodetection
-} stereo_code;
-
-struct vf_priv_s {
- int in_fmt;
- int out_fmt;
- bool auto_in;
- int warn;
- struct vf_lw_opts *lw_opts;
-} const vf_priv_default = {
- SIDE_BY_SIDE_LR,
- ANAGLYPH_RC_DUBOIS,
- .warn = 1,
-};
-
-const struct m_opt_choice_alternatives stereo_code_names[] = {
- {"arcg", ANAGLYPH_RC_GRAY},
- {"anaglyph_red_cyan_gray", ANAGLYPH_RC_GRAY},
- {"arch", ANAGLYPH_RC_HALF},
- {"anaglyph_red_cyan_half_color", ANAGLYPH_RC_HALF},
- {"arcc", ANAGLYPH_RC_COLOR},
- {"anaglyph_red_cyan_color", ANAGLYPH_RC_COLOR},
- {"arcd", ANAGLYPH_RC_DUBOIS},
- {"anaglyph_red_cyan_dubios", ANAGLYPH_RC_DUBOIS},
- {"agmg", ANAGLYPH_GM_GRAY},
- {"anaglyph_green_magenta_gray", ANAGLYPH_GM_GRAY},
- {"agmh", ANAGLYPH_GM_HALF},
- {"anaglyph_green_magenta_half_color",ANAGLYPH_GM_HALF},
- {"agmc", ANAGLYPH_GM_COLOR},
- {"anaglyph_green_magenta_color", ANAGLYPH_GM_COLOR},
- {"agmd", ANAGLYPH_GM_DUBOIS},
- {"anaglyph_green_magenta_dubois", ANAGLYPH_GM_DUBOIS},
- {"aybg", ANAGLYPH_YB_GRAY},
- {"anaglyph_yellow_blue_gray", ANAGLYPH_YB_GRAY},
- {"aybh", ANAGLYPH_YB_HALF},
- {"anaglyph_yellow_blue_half_color", ANAGLYPH_YB_HALF},
- {"aybc", ANAGLYPH_YB_COLOR},
- {"anaglyph_yellow_blue_color", ANAGLYPH_YB_COLOR},
- {"aybd", ANAGLYPH_YB_DUBOIS},
- {"anaglyph_yellow_blue_dubois", ANAGLYPH_YB_DUBOIS},
- {"ml", MONO_L},
- {"mono_left", MONO_L},
- {"mr", MONO_R},
- {"mono_right", MONO_R},
- {"sbsl", SIDE_BY_SIDE_LR},
- {"side_by_side_left_first", SIDE_BY_SIDE_LR},
- {"sbsr", SIDE_BY_SIDE_RL},
- {"side_by_side_right_first", SIDE_BY_SIDE_RL},
- {"sbs2l", SIDE_BY_SIDE_2_LR},
- {"side_by_side_half_width_left_first", SIDE_BY_SIDE_2_LR},
- {"sbs2r", SIDE_BY_SIDE_2_RL},
- {"side_by_side_half_width_right_first",SIDE_BY_SIDE_2_RL},
- {"abl", ABOVE_BELOW_LR},
- {"above_below_left_first", ABOVE_BELOW_LR},
- {"abr", ABOVE_BELOW_RL},
- {"above_below_right_first", ABOVE_BELOW_RL},
- {"ab2l", ABOVE_BELOW_2_LR},
- {"above_below_half_height_left_first", ABOVE_BELOW_2_LR},
- {"ab2r", ABOVE_BELOW_2_RL},
- {"above_below_half_height_right_first",ABOVE_BELOW_2_RL},
- {"irl", INTERLEAVE_ROWS_LR},
- {"interleave_rows_left_first", INTERLEAVE_ROWS_LR},
- {"irr", INTERLEAVE_ROWS_RL},
- {"interleave_rows_right_first", INTERLEAVE_ROWS_RL},
- {"al", ALTERNATING_LR},
- {"ar", ALTERNATING_RL},
- // convenience alias for MP_STEREO3D_MONO
- {"mono", MONO_L},
- // for filter auto-insertion
- {"auto", STEREO_AUTO},
- { NULL, 0}
-};
-
-// Extremely stupid; can be dropped when the internal filter is dropped,
-// and OPT_CHOICE_C() can be used instead.
-static int opt_to_stereo3dmode(int val)
-{
- // Find x for name == MP_STEREO3D_NAME(x)
- const char *name = m_opt_choice_str(stereo_code_names, val);
- for (int n = 0; n < MP_STEREO3D_COUNT; n++) {
- const char *o = MP_STEREO3D_NAME(val);
- if (name && o && strcmp(o, name) == 0)
- return n;
- }
- return MP_STEREO3D_INVALID;
-}
-
-static int lavfi_reconfig(struct vf_instance *vf,
- struct mp_image_params *in,
- struct mp_image_params *out)
-{
- struct vf_priv_s *p = vf_lw_old_priv(vf);
- if (p->auto_in) {
- const char *inf = MP_STEREO3D_NAME(in->stereo_in);
- if (!inf) {
- MP_ERR(vf, "Unknown/unsupported 3D mode.\n");
- return -1;
- }
- vf_lw_update_graph(vf, "stereo3d", "%s:%s",
- inf, m_opt_choice_str(stereo_code_names, p->out_fmt));
- out->stereo_in = out->stereo_out = opt_to_stereo3dmode(p->out_fmt);
- }
- return 0;
-}
-
-static void lavfi_init(vf_instance_t *vf)
-{
- if (vf->priv->in_fmt == STEREO_AUTO &&
- vf_lw_set_graph(vf, vf->priv->lw_opts, "stereo3d", "null") >= 0)
- {
- vf_lw_set_reconfig_cb(vf, lavfi_reconfig);
- return;
- }
-
- if (vf_lw_set_graph(vf, vf->priv->lw_opts, "stereo3d", "%s:%s",
- m_opt_choice_str(stereo_code_names, vf->priv->in_fmt),
- m_opt_choice_str(stereo_code_names, vf->priv->out_fmt)) >= 0)
- return;
-}
-
-static int vf_open(vf_instance_t *vf)
-{
- if (vf->priv->warn)
- MP_WARN(vf, "%s", VF_LW_REPLACE);
-
- if (vf->priv->out_fmt == STEREO_AUTO) {
- MP_FATAL(vf, "No autodetection for stereo output.\n");
- return 0;
- }
- if (vf->priv->in_fmt == STEREO_AUTO)
- vf->priv->auto_in = 1;
-
- lavfi_init(vf);
- return 1;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_CHOICE_C("in", in_fmt, 0, stereo_code_names),
- OPT_CHOICE_C("out", out_fmt, 0, stereo_code_names),
- OPT_FLAG("warn", warn, 0),
- OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
- {0}
-};
-
-const vf_info_t vf_info_stereo3d = {
- .description = "stereoscopic 3d view",
- .name = "stereo3d",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_default,
- .options = vf_opts_fields,
-};
diff --git a/video/filter/vf_sub.c b/video/filter/vf_sub.c
index 184ab8d..065a8c6 100644
--- a/video/filter/vf_sub.c
+++ b/video/filter/vf_sub.c
@@ -3,20 +3,21 @@
*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+
#include "config.h"
#include <stdio.h>
diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c
index 3ed3c01..b5aad7a 100644
--- a/video/filter/vf_vapoursynth.c
+++ b/video/filter/vf_vapoursynth.c
@@ -260,7 +260,7 @@ static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n,
// If these assertions fail, n is an unrequested frame (or filtered twice).
assert(n >= p->out_frameno && n < p->out_frameno + p->max_requests);
int index = n - p->out_frameno;
- MP_DBG(vf, "filtered frame %d (%d)\n", n, index);
+ MP_TRACE(vf, "filtered frame %d (%d)\n", n, index);
assert(p->requested[index] == &dummy_img);
struct mp_image *res = NULL;
@@ -328,7 +328,7 @@ static bool locked_read_output(struct vf_instance *vf)
// infiltGetFrame (if it does, we would deadlock)
p->requested[n] = (struct mp_image *)&dummy_img;
p->failed = false;
- MP_DBG(vf, "requesting frame %d (%d)\n", p->out_frameno + n, n);
+ MP_TRACE(vf, "requesting frame %d (%d)\n", p->out_frameno + n, n);
p->vsapi->getFrameAsync(p->out_frameno + n, p->out_node,
vs_frame_done, vf);
}
@@ -463,7 +463,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason,
VSFrameRef *ret = NULL;
pthread_mutex_lock(&p->lock);
- MP_DBG(vf, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno);
+ MP_TRACE(vf, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno);
while (1) {
if (p->shutdown) {
p->vsapi->setFilterError("EOF or filter reinit/uninit", frameCtx);
diff --git a/video/filter/vf_vavpp.c b/video/filter/vf_vavpp.c
index 4b225aa..edee556 100644
--- a/video/filter/vf_vavpp.c
+++ b/video/filter/vf_vavpp.c
@@ -20,22 +20,18 @@
#include <va/va.h>
#include <va/va_vpp.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+
#include "config.h"
#include "options/options.h"
#include "vf.h"
#include "refqueue.h"
+#include "video/fmt-conversion.h"
#include "video/vaapi.h"
#include "video/hwdec.h"
#include "video/mp_image_pool.h"
-static bool check_error(struct vf_instance *vf, VAStatus status, const char *msg)
-{
- if (status == VA_STATUS_SUCCESS)
- return true;
- MP_ERR(vf, "%s: %s\n", msg, vaErrorStr(status));
- return false;
-}
-
struct surface_refs {
VASurfaceID *surfaces;
int num_surfaces;
@@ -62,10 +58,9 @@ struct vf_priv_s {
VAContextID context;
struct mp_image_params params;
VADisplay display;
- struct mp_vaapi_ctx *va;
+ AVBufferRef *av_device_ref;
struct pipeline pipe;
- struct mp_image_pool *pool;
- int current_rt_format;
+ AVBufferRef *hw_pool;
struct mp_refqueue *queue;
};
@@ -130,7 +125,7 @@ static void update_pipeline(struct vf_instance *vf)
};
VAStatus status = vaQueryVideoProcPipelineCaps(p->display, p->context,
filters, num_filters, &caps);
- if (!check_error(vf, status, "vaQueryVideoProcPipelineCaps()"))
+ if (!CHECK_VA_STATUS(vf, "vaQueryVideoProcPipelineCaps()"))
goto nodeint;
p->pipe.filters = filters;
p->pipe.num_filters = num_filters;
@@ -156,6 +151,28 @@ nodeint:
mp_refqueue_set_mode(p->queue, 0);
}
+static struct mp_image *alloc_out(struct vf_instance *vf)
+{
+ struct vf_priv_s *p = vf->priv;
+
+ AVFrame *av_frame = av_frame_alloc();
+ if (!av_frame)
+ abort();
+ if (av_hwframe_get_buffer(p->hw_pool, av_frame, 0) < 0) {
+ MP_ERR(vf, "Failed to allocate frame from hw pool.\n");
+ av_frame_free(&av_frame);
+ return NULL;
+ }
+ struct mp_image *img = mp_image_from_av_frame(av_frame);
+ av_frame_free(&av_frame);
+ if (!img) {
+ MP_ERR(vf, "Unknown error.\n");
+ return NULL;
+ }
+ mp_image_set_size(img, vf->fmt_in.w, vf->fmt_in.h);
+ return img;
+}
+
static struct mp_image *render(struct vf_instance *vf)
{
struct vf_priv_s *p = vf->priv;
@@ -167,15 +184,13 @@ static struct mp_image *render(struct vf_instance *vf)
VABufferID buffer = VA_INVALID_ID;
VASurfaceID in_id = va_surface_id(in);
- if (!p->pipe.filters || in_id == VA_INVALID_ID)
+ if (!p->pipe.filters || in_id == VA_INVALID_ID || !p->hw_pool)
goto cleanup;
- int r_w, r_h;
- va_surface_get_uncropped_size(in, &r_w, &r_h);
- img = mp_image_pool_get(p->pool, IMGFMT_VAAPI, r_w, r_h);
+ img = alloc_out(vf);
if (!img)
goto cleanup;
- mp_image_set_size(img, in->w, in->h);
+
mp_image_copy_attributes(img, in);
unsigned int flags = va_get_colorspace_flag(p->params.color.space);
@@ -192,7 +207,7 @@ static struct mp_image *render(struct vf_instance *vf)
goto cleanup;
VAStatus status = vaBeginPicture(p->display, p->context, id);
- if (!check_error(vf, status, "vaBeginPicture()"))
+ if (!CHECK_VA_STATUS(vf, "vaBeginPicture()"))
goto cleanup;
need_end_picture = true;
@@ -201,12 +216,12 @@ static struct mp_image *render(struct vf_instance *vf)
status = vaCreateBuffer(p->display, p->context,
VAProcPipelineParameterBufferType,
sizeof(*param), 1, NULL, &buffer);
- if (!check_error(vf, status, "vaCreateBuffer()"))
+ if (!CHECK_VA_STATUS(vf, "vaCreateBuffer()"))
goto cleanup;
VAProcFilterParameterBufferDeinterlacing *filter_params;
status = vaMapBuffer(p->display, *(p->pipe.filters), (void**)&filter_params);
- if (!check_error(vf, status, "vaMapBuffer()"))
+ if (!CHECK_VA_STATUS(vf, "vaMapBuffer()"))
goto cleanup;
filter_params->flags = flags & VA_TOP_FIELD ? 0 : VA_DEINTERLACING_BOTTOM_FIELD;
@@ -216,7 +231,7 @@ static struct mp_image *render(struct vf_instance *vf)
vaUnmapBuffer(p->display, *(p->pipe.filters));
status = vaMapBuffer(p->display, buffer, (void**)&param);
- if (!check_error(vf, status, "vaMapBuffer()"))
+ if (!CHECK_VA_STATUS(vf, "vaMapBuffer()"))
goto cleanup;
*param = (VAProcPipelineParameterBuffer){0};
@@ -247,7 +262,7 @@ static struct mp_image *render(struct vf_instance *vf)
vaUnmapBuffer(p->display, buffer);
status = vaRenderPicture(p->display, p->context, &buffer, 1);
- if (!check_error(vf, status, "vaRenderPicture()"))
+ if (!CHECK_VA_STATUS(vf, "vaRenderPicture()"))
goto cleanup;
success = true;
@@ -264,11 +279,12 @@ cleanup:
static struct mp_image *upload(struct vf_instance *vf, struct mp_image *in)
{
- struct vf_priv_s *p = vf->priv;
- struct mp_image *out = mp_image_pool_get(p->pool, IMGFMT_VAAPI, in->w, in->h);
+ // Since we do no scaling or csp conversion, we can allocate an output
+ // surface for input too.
+ struct mp_image *out = alloc_out(vf);
if (!out)
return NULL;
- if (va_surface_upload(out, in) < 0) {
+ if (!mp_image_hw_upload(out, in)) {
talloc_free(out);
return NULL;
}
@@ -323,23 +339,39 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
struct vf_priv_s *p = vf->priv;
flush_frames(vf);
- talloc_free(p->pool);
- p->pool = NULL;
+ av_buffer_unref(&p->hw_pool);
p->params = *in;
+ *out = *in;
- p->current_rt_format = VA_RT_FORMAT_YUV420;
- p->pool = mp_image_pool_new(20);
- va_pool_set_allocator(p->pool, p->va, p->current_rt_format);
+ int src_w = in->w;
+ int src_h = in->h;
+
+ if (in->imgfmt == IMGFMT_VAAPI) {
+ if (!vf->in_hwframes_ref)
+ return -1;
+ AVHWFramesContext *hw_frames = (void *)vf->in_hwframes_ref->data;
+ // VAAPI requires the full surface size to match for input and output.
+ src_w = hw_frames->width;
+ src_h = hw_frames->height;
+ } else {
+ out->imgfmt = IMGFMT_VAAPI;
+ out->hw_subfmt = IMGFMT_NV12;
+ }
- struct mp_image *probe = mp_image_pool_get(p->pool, IMGFMT_VAAPI, in->w, in->h);
- if (!probe)
+ p->hw_pool = av_hwframe_ctx_alloc(p->av_device_ref);
+ if (!p->hw_pool)
return -1;
- va_surface_init_subformat(probe);
- *out = *in;
- out->imgfmt = probe->params.imgfmt;
- out->hw_subfmt = probe->params.hw_subfmt;
- talloc_free(probe);
+ AVHWFramesContext *hw_frames = (void *)p->hw_pool->data;
+ hw_frames->format = AV_PIX_FMT_VAAPI;
+ hw_frames->sw_format = imgfmt2pixfmt(out->hw_subfmt);
+ hw_frames->width = src_w;
+ hw_frames->height = src_h;
+ if (av_hwframe_ctx_init(p->hw_pool) < 0) {
+ MP_ERR(vf, "Failed to initialize libavutil vaapi frames pool.\n");
+ av_buffer_unref(&p->hw_pool);
+ return -1;
+ }
return 0;
}
@@ -353,15 +385,15 @@ static void uninit(struct vf_instance *vf)
vaDestroyContext(p->display, p->context);
if (p->config != VA_INVALID_ID)
vaDestroyConfig(p->display, p->config);
- talloc_free(p->pool);
+ av_buffer_unref(&p->hw_pool);
flush_frames(vf);
mp_refqueue_free(p->queue);
+ av_buffer_unref(&p->av_device_ref);
}
static int query_format(struct vf_instance *vf, unsigned int imgfmt)
{
- struct vf_priv_s *p = vf->priv;
- if (imgfmt == IMGFMT_VAAPI || va_image_format_from_imgfmt(p->va, imgfmt))
+ if (imgfmt == IMGFMT_VAAPI || imgfmt == IMGFMT_NV12 || imgfmt == IMGFMT_420P)
return vf_next_query_format(vf, IMGFMT_VAAPI);
return 0;
}
@@ -383,7 +415,7 @@ static int va_query_filter_caps(struct vf_instance *vf, VAProcFilterType type,
struct vf_priv_s *p = vf->priv;
VAStatus status = vaQueryVideoProcFilterCaps(p->display, p->context, type,
caps, &count);
- return check_error(vf, status, "vaQueryVideoProcFilterCaps()") ? count : 0;
+ return CHECK_VA_STATUS(vf, "vaQueryVideoProcFilterCaps()") ? count : 0;
}
static VABufferID va_create_filter_buffer(struct vf_instance *vf, int bytes,
@@ -394,7 +426,7 @@ static VABufferID va_create_filter_buffer(struct vf_instance *vf, int bytes,
VAStatus status = vaCreateBuffer(p->display, p->context,
VAProcFilterParameterBufferType,
bytes, num, data, &buffer);
- return check_error(vf, status, "vaCreateBuffer()") ? buffer : VA_INVALID_ID;
+ return CHECK_VA_STATUS(vf, "vaCreateBuffer()") ? buffer : VA_INVALID_ID;
}
static bool initialize(struct vf_instance *vf)
@@ -405,20 +437,20 @@ static bool initialize(struct vf_instance *vf)
VAConfigID config;
status = vaCreateConfig(p->display, VAProfileNone, VAEntrypointVideoProc,
NULL, 0, &config);
- if (!check_error(vf, status, "vaCreateConfig()")) // no entrypoint for video porc
+ if (!CHECK_VA_STATUS(vf, "vaCreateConfig()")) // no entrypoint for video porc
return false;
p->config = config;
VAContextID context;
status = vaCreateContext(p->display, p->config, 0, 0, 0, NULL, 0, &context);
- if (!check_error(vf, status, "vaCreateContext()"))
+ if (!CHECK_VA_STATUS(vf, "vaCreateContext()"))
return false;
p->context = context;
VAProcFilterType filters[VAProcFilterCount];
int num_filters = VAProcFilterCount;
status = vaQueryVideoProcFilters(p->display, p->context, filters, &num_filters);
- if (!check_error(vf, status, "vaQueryVideoProcFilters()"))
+ if (!CHECK_VA_STATUS(vf, "vaQueryVideoProcFilters()"))
return false;
VABufferID buffers[VAProcFilterCount];
@@ -459,6 +491,9 @@ static int vf_open(vf_instance_t *vf)
{
struct vf_priv_s *p = vf->priv;
+ if (!vf->hwdec_devs)
+ return 0;
+
vf->reconfig = reconfig;
vf->filter_ext = filter_ext;
vf->filter_out = filter_out;
@@ -468,10 +503,19 @@ static int vf_open(vf_instance_t *vf)
p->queue = mp_refqueue_alloc();
- p->va = hwdec_devices_load(vf->hwdec_devs, HWDEC_VAAPI);
- if (!p->va)
+ hwdec_devices_request_all(vf->hwdec_devs);
+ p->av_device_ref =
+ hwdec_devices_get_lavc(vf->hwdec_devs, AV_HWDEVICE_TYPE_VAAPI);
+ if (!p->av_device_ref) {
+ uninit(vf);
return 0;
- p->display = p->va->display;
+ }
+
+ AVHWDeviceContext *hwctx = (void *)p->av_device_ref->data;
+ AVVAAPIDeviceContext *vactx = hwctx->hwctx;
+
+ p->display = vactx->display;
+
if (initialize(vf))
return true;
uninit(vf);
diff --git a/video/filter/vf_vdpaupp.c b/video/filter/vf_vdpaupp.c
index a583e38..de1979c 100644
--- a/video/filter/vf_vdpaupp.c
+++ b/video/filter/vf_vdpaupp.c
@@ -21,6 +21,8 @@
#include <inttypes.h>
#include <assert.h>
+#include <libavutil/hwcontext.h>
+
#include "common/common.h"
#include "common/msg.h"
#include "options/m_option.h"
@@ -166,6 +168,9 @@ static int vf_open(vf_instance_t *vf)
{
struct vf_priv_s *p = vf->priv;
+ if (!vf->hwdec_devs)
+ return 0;
+
vf->reconfig = reconfig;
vf->filter_ext = filter_ext;
vf->filter_out = filter_out;
@@ -175,9 +180,15 @@ static int vf_open(vf_instance_t *vf)
p->queue = mp_refqueue_alloc();
- p->ctx = hwdec_devices_load(vf->hwdec_devs, HWDEC_VDPAU);
- if (!p->ctx)
+ hwdec_devices_request_all(vf->hwdec_devs);
+ AVBufferRef *ref =
+ hwdec_devices_get_lavc(vf->hwdec_devs, AV_HWDEVICE_TYPE_VDPAU);
+ struct mp_vdpau_ctx *ctx = mp_vdpau_get_ctx_from_av(ref);
+ av_buffer_unref(&ref);
+ if (!ctx) {
+ uninit(vf);
return 0;
+ }
p->def_deintmode = p->opts.deint;
if (!p->deint_enabled)
diff --git a/video/filter/vf_yadif.c b/video/filter/vf_yadif.c
deleted file mode 100644
index e044dcc..0000000
--- a/video/filter/vf_yadif.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2006 Michael Niedermayer <michaelni@gmx.at>
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdlib.h>
-
-#include <libavfilter/version.h>
-
-#include "options/options.h"
-
-#include "common/msg.h"
-#include "vf.h"
-
-#include "vf_lavfi.h"
-
-struct vf_priv_s {
- int mode;
- int interlaced_only;
- struct vf_lw_opts *lw_opts;
- int warn;
-};
-
-static int vf_open(vf_instance_t *vf)
-{
- struct vf_priv_s *p = vf->priv;
-
- if (p->warn)
- MP_WARN(vf, "%s", VF_LW_REPLACE);
-
-#if LIBAVFILTER_VERSION_MICRO >= 100
- const char *mode[] = {"send_frame", "send_field", "send_frame_nospatial",
- "send_field_nospatial"};
-
- if (vf_lw_set_graph(vf, p->lw_opts, "yadif", "mode=%s:deint=%s", mode[p->mode],
- p->interlaced_only ? "interlaced" : "all") >= 0)
- {
- return 1;
- }
-#else
- // Libav numeric modes happen to match ours, but keep it explicit.
- const char *mode[] = {"0", "1", "2", "3"};
- if (vf_lw_set_graph(vf, p->lw_opts, "yadif", "mode=%s:auto=%d", mode[p->mode],
- p->interlaced_only) >= 0)
- {
- return 1;
- }
-#endif
-
- MP_FATAL(vf, "This version of libavfilter has no 'yadif' filter.\n");
- return 0;
-}
-
-#define OPT_BASE_STRUCT struct vf_priv_s
-static const m_option_t vf_opts_fields[] = {
- OPT_CHOICE("mode", mode, 0,
- ({"frame", 0},
- {"field", 1},
- {"frame-nospatial", 2},
- {"field-nospatial", 3})),
- OPT_FLAG("interlaced-only", interlaced_only, 0),
- OPT_FLAG("warn", warn, 0),
- OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
- {0}
-};
-
-const vf_info_t vf_info_yadif = {
- .description = "Yet Another DeInterlacing Filter",
- .name = "yadif",
- .open = vf_open,
- .priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &(const struct vf_priv_s){
- .mode = 1,
- .interlaced_only = 1,
- .warn = 1,
- },
- .options = vf_opts_fields,
-};
diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c
index 6d14c28..e89ea6c 100644
--- a/video/fmt-conversion.c
+++ b/video/fmt-conversion.c
@@ -61,21 +61,23 @@ static const struct {
#if HAVE_VIDEOTOOLBOX_HWACCEL
{IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX},
#endif
- {IMGFMT_VAAPI, AV_PIX_FMT_VAAPI_VLD},
+#if HAVE_ANDROID
+ {IMGFMT_MEDIACODEC, AV_PIX_FMT_MEDIACODEC},
+#endif
+ {IMGFMT_VAAPI, AV_PIX_FMT_VAAPI},
{IMGFMT_DXVA2, AV_PIX_FMT_DXVA2_VLD},
#if HAVE_D3D_HWACCEL
-#if HAVE_D3D_HWACCEL_NEW
{IMGFMT_D3D11VA, AV_PIX_FMT_D3D11},
{IMGFMT_D3D11NV12, AV_PIX_FMT_D3D11},
-#else
- {IMGFMT_D3D11VA, AV_PIX_FMT_D3D11VA_VLD},
-#endif
#endif
{IMGFMT_MMAL, AV_PIX_FMT_MMAL},
#if HAVE_CUDA_HWACCEL
{IMGFMT_CUDA, AV_PIX_FMT_CUDA},
#endif
{IMGFMT_P010, AV_PIX_FMT_P010},
+#if HAVE_DRMPRIME
+ {IMGFMT_DRMPRIME, AV_PIX_FMT_DRM_PRIME},
+#endif
{0, AV_PIX_FMT_NONE}
};
diff --git a/video/gpu_memcpy.c b/video/gpu_memcpy.c
deleted file mode 100644
index 542fbc8..0000000
--- a/video/gpu_memcpy.c
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2011-2014 Hendrik Leppkes
- * http://www.1f0.de
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Taken from the QuickSync decoder by Eric Gur
- */
-
-#pragma GCC push_options
-#pragma GCC target("sse4.1")
-#include <smmintrin.h>
-
-#include <stdbool.h>
-#include <string.h>
-
-#include "gpu_memcpy.h"
-
-// gpu_memcpy is a memcpy style function that copied data very fast from a
-// GPU tiled memory (write back)
-// Performance tip: page offset (12 lsb) of both addresses should be different
-// optimally use a 2K offset between them.
-void *gpu_memcpy(void *restrict d, const void *restrict s, size_t size)
-{
- static const size_t regsInLoop = sizeof(size_t) * 2; // 8 or 16
-
- if (d == NULL || s == NULL) return NULL;
-
- // If memory is not aligned, use memcpy
- bool isAligned = (((size_t)(s) | (size_t)(d)) & 0xF) == 0;
- if (!isAligned)
- {
- return memcpy(d, s, size);
- }
-
- __m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7;
-#ifdef __x86_64__
- __m128i xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15;
-#endif
-
- size_t reminder = size & (regsInLoop * sizeof(xmm0) - 1); // Copy 128 or 256 bytes every loop
- size_t end = 0;
-
- __m128i* pTrg = (__m128i*)d;
- __m128i* pTrgEnd = pTrg + ((size - reminder) >> 4);
- __m128i* pSrc = (__m128i*)s;
-
- // Make sure source is synced - doesn't hurt if not needed.
- _mm_sfence();
-
- while (pTrg < pTrgEnd)
- {
- // _mm_stream_load_si128 emits the Streaming SIMD Extensions 4 (SSE4.1) instruction MOVNTDQA
- // Fastest method for copying GPU RAM. Available since Penryn (45nm Core 2 Duo/Quad)
- xmm0 = _mm_stream_load_si128(pSrc);
- xmm1 = _mm_stream_load_si128(pSrc + 1);
- xmm2 = _mm_stream_load_si128(pSrc + 2);
- xmm3 = _mm_stream_load_si128(pSrc + 3);
- xmm4 = _mm_stream_load_si128(pSrc + 4);
- xmm5 = _mm_stream_load_si128(pSrc + 5);
- xmm6 = _mm_stream_load_si128(pSrc + 6);
- xmm7 = _mm_stream_load_si128(pSrc + 7);
-#ifdef __x86_64__ // Use all 16 xmm registers
- xmm8 = _mm_stream_load_si128(pSrc + 8);
- xmm9 = _mm_stream_load_si128(pSrc + 9);
- xmm10 = _mm_stream_load_si128(pSrc + 10);
- xmm11 = _mm_stream_load_si128(pSrc + 11);
- xmm12 = _mm_stream_load_si128(pSrc + 12);
- xmm13 = _mm_stream_load_si128(pSrc + 13);
- xmm14 = _mm_stream_load_si128(pSrc + 14);
- xmm15 = _mm_stream_load_si128(pSrc + 15);
-#endif
- pSrc += regsInLoop;
- // _mm_store_si128 emit the SSE2 instruction MOVDQA (aligned store)
- _mm_store_si128(pTrg , xmm0);
- _mm_store_si128(pTrg + 1, xmm1);
- _mm_store_si128(pTrg + 2, xmm2);
- _mm_store_si128(pTrg + 3, xmm3);
- _mm_store_si128(pTrg + 4, xmm4);
- _mm_store_si128(pTrg + 5, xmm5);
- _mm_store_si128(pTrg + 6, xmm6);
- _mm_store_si128(pTrg + 7, xmm7);
-#ifdef __x86_64__ // Use all 16 xmm registers
- _mm_store_si128(pTrg + 8, xmm8);
- _mm_store_si128(pTrg + 9, xmm9);
- _mm_store_si128(pTrg + 10, xmm10);
- _mm_store_si128(pTrg + 11, xmm11);
- _mm_store_si128(pTrg + 12, xmm12);
- _mm_store_si128(pTrg + 13, xmm13);
- _mm_store_si128(pTrg + 14, xmm14);
- _mm_store_si128(pTrg + 15, xmm15);
-#endif
- pTrg += regsInLoop;
- }
-
- // Copy in 16 byte steps
- if (reminder >= 16)
- {
- size = reminder;
- reminder = size & 15;
- end = size >> 4;
- for (size_t i = 0; i < end; ++i)
- {
- pTrg[i] = _mm_stream_load_si128(pSrc + i);
- }
- }
-
- // Copy last bytes - shouldn't happen as strides are modulu 16
- if (reminder)
- {
- __m128i temp = _mm_stream_load_si128(pSrc + end);
-
- char* ps = (char*)(&temp);
- char* pt = (char*)(pTrg + end);
-
- for (size_t i = 0; i < reminder; ++i)
- {
- pt[i] = ps[i];
- }
- }
-
- return d;
-}
diff --git a/video/gpu_memcpy.h b/video/gpu_memcpy.h
deleted file mode 100644
index c62f754..0000000
--- a/video/gpu_memcpy.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef GPU_MEMCPY_SSE4_H_
-#define GPU_MEMCPY_SSE4_H_
-
-#include <stddef.h>
-
-void *gpu_memcpy(void *restrict d, const void *restrict s, size_t size);
-
-#endif
diff --git a/video/hwdec.c b/video/hwdec.c
index 371c368..b52b082 100644
--- a/video/hwdec.c
+++ b/video/hwdec.c
@@ -1,14 +1,19 @@
#include <pthread.h>
#include <assert.h>
+#include <libavutil/hwcontext.h>
+
+#include "config.h"
+
#include "hwdec.h"
struct mp_hwdec_devices {
pthread_mutex_t lock;
- struct mp_hwdec_ctx *hwctx;
+ struct mp_hwdec_ctx **hwctxs;
+ int num_hwctxs;
- void (*load_api)(void *ctx, enum hwdec_type type);
+ void (*load_api)(void *ctx);
void *load_api_ctx;
};
@@ -23,19 +28,28 @@ void hwdec_devices_destroy(struct mp_hwdec_devices *devs)
{
if (!devs)
return;
- assert(!devs->hwctx); // must have been hwdec_devices_remove()ed
+ assert(!devs->num_hwctxs); // must have been hwdec_devices_remove()ed
assert(!devs->load_api); // must have been unset
pthread_mutex_destroy(&devs->lock);
talloc_free(devs);
}
-struct mp_hwdec_ctx *hwdec_devices_get(struct mp_hwdec_devices *devs,
- enum hwdec_type type)
+struct AVBufferRef *hwdec_devices_get_lavc(struct mp_hwdec_devices *devs,
+ int av_hwdevice_type)
{
- struct mp_hwdec_ctx *res = NULL;
+ AVBufferRef *res = NULL;
pthread_mutex_lock(&devs->lock);
- if (devs->hwctx && devs->hwctx->type == type)
- res = devs->hwctx;
+ for (int n = 0; n < devs->num_hwctxs; n++) {
+ struct mp_hwdec_ctx *dev = devs->hwctxs[n];
+ if (dev->av_device_ref) {
+ AVHWDeviceContext *hwctx = (void *)dev->av_device_ref->data;
+ if (hwctx->type == av_hwdevice_type) {
+ if (dev->av_device_ref)
+ res = av_buffer_ref(dev->av_device_ref);
+ break;
+ }
+ }
+ }
pthread_mutex_unlock(&devs->lock);
return res;
}
@@ -43,7 +57,7 @@ struct mp_hwdec_ctx *hwdec_devices_get(struct mp_hwdec_devices *devs,
struct mp_hwdec_ctx *hwdec_devices_get_first(struct mp_hwdec_devices *devs)
{
pthread_mutex_lock(&devs->lock);
- struct mp_hwdec_ctx *res = devs->hwctx;
+ struct mp_hwdec_ctx *res = devs->num_hwctxs ? devs->hwctxs[0] : NULL;
pthread_mutex_unlock(&devs->lock);
return res;
}
@@ -51,38 +65,67 @@ struct mp_hwdec_ctx *hwdec_devices_get_first(struct mp_hwdec_devices *devs)
void hwdec_devices_add(struct mp_hwdec_devices *devs, struct mp_hwdec_ctx *ctx)
{
pthread_mutex_lock(&devs->lock);
- // We support only 1 device; ignore the rest.
- if (!devs->hwctx)
- devs->hwctx = ctx;
+ MP_TARRAY_APPEND(devs, devs->hwctxs, devs->num_hwctxs, ctx);
pthread_mutex_unlock(&devs->lock);
}
void hwdec_devices_remove(struct mp_hwdec_devices *devs, struct mp_hwdec_ctx *ctx)
{
pthread_mutex_lock(&devs->lock);
- if (devs->hwctx == ctx)
- devs->hwctx = NULL;
+ for (int n = 0; n < devs->num_hwctxs; n++) {
+ if (devs->hwctxs[n] == ctx) {
+ MP_TARRAY_REMOVE_AT(devs->hwctxs, devs->num_hwctxs, n);
+ break;
+ }
+ }
pthread_mutex_unlock(&devs->lock);
}
void hwdec_devices_set_loader(struct mp_hwdec_devices *devs,
- void (*load_api)(void *ctx, enum hwdec_type type), void *load_api_ctx)
+ void (*load_api)(void *ctx), void *load_api_ctx)
{
devs->load_api = load_api;
devs->load_api_ctx = load_api_ctx;
}
-void hwdec_devices_request(struct mp_hwdec_devices *devs, enum hwdec_type type)
+void hwdec_devices_request_all(struct mp_hwdec_devices *devs)
{
if (devs->load_api && !hwdec_devices_get_first(devs))
- devs->load_api(devs->load_api_ctx, type);
+ devs->load_api(devs->load_api_ctx);
}
-void *hwdec_devices_load(struct mp_hwdec_devices *devs, enum hwdec_type type)
+char *hwdec_devices_get_names(struct mp_hwdec_devices *devs)
{
- if (!devs)
- return NULL;
- hwdec_devices_request(devs, type);
- struct mp_hwdec_ctx *hwctx = hwdec_devices_get(devs, type);
- return hwctx ? hwctx->ctx : NULL;
+ char *res = NULL;
+ for (int n = 0; n < devs->num_hwctxs; n++) {
+ if (res)
+ ta_xstrdup_append(&res, ",");
+ ta_xstrdup_append(&res, devs->hwctxs[n]->driver_name);
+ }
+ return res;
+}
+
+static const struct hwcontext_fns *const hwcontext_fns[] = {
+#if HAVE_D3D_HWACCEL
+ &hwcontext_fns_d3d11,
+#endif
+#if HAVE_D3D9_HWACCEL
+ &hwcontext_fns_dxva2,
+#endif
+#if HAVE_VAAPI
+ &hwcontext_fns_vaapi,
+#endif
+#if HAVE_VDPAU
+ &hwcontext_fns_vdpau,
+#endif
+ NULL,
+};
+
+const struct hwcontext_fns *hwdec_get_hwcontext_fns(int av_hwdevice_type)
+{
+ for (int n = 0; hwcontext_fns[n]; n++) {
+ if (hwcontext_fns[n]->av_hwdevice_type == av_hwdevice_type)
+ return hwcontext_fns[n];
+ }
+ return NULL;
}
diff --git a/video/hwdec.h b/video/hwdec.h
index 637014d..1022654 100644
--- a/video/hwdec.h
+++ b/video/hwdec.h
@@ -1,75 +1,20 @@
#ifndef MP_HWDEC_H_
#define MP_HWDEC_H_
+#include <libavutil/buffer.h>
+
#include "options/m_option.h"
struct mp_image_pool;
-// keep in sync with --hwdec option (see mp_hwdec_names)
-enum hwdec_type {
- HWDEC_NONE = 0,
- HWDEC_AUTO,
- HWDEC_AUTO_COPY,
- HWDEC_VDPAU,
- HWDEC_VDPAU_COPY,
- HWDEC_VIDEOTOOLBOX,
- HWDEC_VIDEOTOOLBOX_COPY,
- HWDEC_VAAPI,
- HWDEC_VAAPI_COPY,
- HWDEC_DXVA2,
- HWDEC_DXVA2_COPY,
- HWDEC_D3D11VA,
- HWDEC_D3D11VA_COPY,
- HWDEC_RPI,
- HWDEC_RPI_COPY,
- HWDEC_MEDIACODEC,
- HWDEC_CUDA,
- HWDEC_CUDA_COPY,
- HWDEC_CRYSTALHD,
-};
-
-#define HWDEC_IS_AUTO(x) ((x) == HWDEC_AUTO || (x) == HWDEC_AUTO_COPY)
-
-// hwdec_type names (options.c)
-extern const struct m_opt_choice_alternatives mp_hwdec_names[];
-
struct mp_hwdec_ctx {
- enum hwdec_type type; // (never HWDEC_NONE or HWDEC_IS_AUTO)
const char *driver_name; // NULL if unknown/not loaded
- // This is never NULL. Its meaning depends on the .type field:
- // HWDEC_VDPAU: struct mp_vdpau_ctx*
- // HWDEC_VIDEOTOOLBOX: non-NULL dummy pointer
- // HWDEC_VAAPI: struct mp_vaapi_ctx*
- // HWDEC_D3D11VA: ID3D11Device*
- // HWDEC_DXVA2: IDirect3DDevice9*
- // HWDEC_CUDA: CUcontext*
- void *ctx;
-
// libavutil-wrapped context, if available.
struct AVBufferRef *av_device_ref; // AVHWDeviceContext*
// List of IMGFMT_s, terminated with 0. NULL if N/A.
int *supported_formats;
-
- // Hint to generic code: it's using a wrapper API
- bool emulated;
-
- // Optional. Legacy. (New code should use AVHWFramesContext and
- // mp_image_hw_download().)
- // Allocates a software image from the pool, downloads the hw image from
- // mpi, and returns it.
- // pool can be NULL (then just use straight allocation).
- // Return NULL on error or if mpi has the wrong format.
- struct mp_image *(*download_image)(struct mp_hwdec_ctx *ctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool);
-
- // Optional. Crap for vdpau. Makes sure preemption recovery is run if needed.
- void (*restore_device)(struct mp_hwdec_ctx *ctx);
-
- // Optional. Do not set for VO-bound devices.
- void (*destroy)(struct mp_hwdec_ctx *ctx);
};
// Used to communicate hardware decoder device handles from VO to video decoder.
@@ -82,8 +27,10 @@ void hwdec_devices_destroy(struct mp_hwdec_devices *devs);
// available. Logically, the returned pointer remains valid until VO
// uninitialization is started (all users of it must be uninitialized before).
// hwdec_devices_request() may be used before this to lazily load devices.
-struct mp_hwdec_ctx *hwdec_devices_get(struct mp_hwdec_devices *devs,
- enum hwdec_type type);
+// Contains a wrapped AVHWDeviceContext.
+// Beware that this creates a _new_ reference.
+struct AVBufferRef *hwdec_devices_get_lavc(struct mp_hwdec_devices *devs,
+ int av_hwdevice_type);
// For code which still strictly assumes there is 1 (or none) device.
struct mp_hwdec_ctx *hwdec_devices_get_first(struct mp_hwdec_devices *devs);
@@ -93,24 +40,56 @@ struct mp_hwdec_ctx *hwdec_devices_get_first(struct mp_hwdec_devices *devs);
void hwdec_devices_add(struct mp_hwdec_devices *devs, struct mp_hwdec_ctx *ctx);
// Remove this from the list of internal devices. Idempotent/ignores entries
-// not added yet.
+// not added yet. This is not thread-safe.
void hwdec_devices_remove(struct mp_hwdec_devices *devs, struct mp_hwdec_ctx *ctx);
// Can be used to enable lazy loading of an API with hwdec_devices_request().
// If used at all, this must be set/unset during initialization/uninitialization,
// as concurrent use with hwdec_devices_request() is a race condition.
void hwdec_devices_set_loader(struct mp_hwdec_devices *devs,
- void (*load_api)(void *ctx, enum hwdec_type type), void *load_api_ctx);
-
-// Cause VO to lazily load the requested device, and will block until this is
-// done (even if not available).
-void hwdec_devices_request(struct mp_hwdec_devices *devs, enum hwdec_type type);
-
-// Convenience function:
-// - return NULL if devs==NULL
-// - call hwdec_devices_request(devs, type)
-// - call hwdec_devices_get(devs, type)
-// - then return the mp_hwdec_ctx.ctx field
-void *hwdec_devices_load(struct mp_hwdec_devices *devs, enum hwdec_type type);
+ void (*load_api)(void *ctx), void *load_api_ctx);
+
+// Cause VO to lazily load all devices, and will block until this is done (even
+// if not available).
+void hwdec_devices_request_all(struct mp_hwdec_devices *devs);
+
+// Return "," concatenated list (for introspection/debugging). Use talloc_free().
+char *hwdec_devices_get_names(struct mp_hwdec_devices *devs);
+
+struct mp_image;
+struct mpv_global;
+
+struct hwcontext_create_dev_params {
+ bool probing; // if true, don't log errors if unavailable
+};
+
+// Per AV_HWDEVICE_TYPE_* functions, queryable via hwdec_get_hwcontext_fns().
+// All entries are strictly optional.
+struct hwcontext_fns {
+ int av_hwdevice_type;
+ // Set any mp_image fields that require hwcontext specific code, such as
+ // fields or flags not present in AVFrame or AVHWFramesContext in a
+ // portable way. This is called directly after img is converted from an
+ // AVFrame, with all other fields already set. img.hwctx will be set, and
+ // use the correct AV_HWDEVICE_TYPE_.
+ void (*complete_image_params)(struct mp_image *img);
+ // Fill in special format-specific requirements.
+ void (*refine_hwframes)(struct AVBufferRef *hw_frames_ctx);
+ // Returns a AVHWDeviceContext*. Used for copy hwdecs.
+ struct AVBufferRef *(*create_dev)(struct mpv_global *global,
+ struct mp_log *log,
+ struct hwcontext_create_dev_params *params);
+ // Return whether this is using some sort of sub-optimal emulation layer.
+ bool (*is_emulated)(struct AVBufferRef *hw_device_ctx);
+};
+
+// The parameter is of type enum AVHWDeviceType (as in int to avoid extensive
+// recursive includes). May return NULL for unknown device types.
+const struct hwcontext_fns *hwdec_get_hwcontext_fns(int av_hwdevice_type);
+
+extern const struct hwcontext_fns hwcontext_fns_d3d11;
+extern const struct hwcontext_fns hwcontext_fns_dxva2;
+extern const struct hwcontext_fns hwcontext_fns_vaapi;
+extern const struct hwcontext_fns hwcontext_fns_vdpau;
#endif
diff --git a/video/img_format.c b/video/img_format.c
index 802528a..12822b1 100644
--- a/video/img_format.c
+++ b/video/img_format.c
@@ -129,7 +129,7 @@ struct mp_imgfmt_desc mp_imgfmt_get_desc(int mpfmt)
fmt == AV_PIX_FMT_UYYVYY411)
return mp_only_imgfmt_desc(mpfmt);
enum mp_component_type is_uint =
- mp_imgfmt_get_component_type(fmt) == MP_COMPONENT_TYPE_UINT;
+ mp_imgfmt_get_component_type(mpfmt) == MP_COMPONENT_TYPE_UINT;
struct mp_imgfmt_desc desc = {
.id = mpfmt,
@@ -321,8 +321,8 @@ static bool validate_regular_imgfmt(const struct mp_regular_imgfmt *fmt)
enum mp_csp mp_imgfmt_get_forced_csp(int imgfmt)
{
- const AVPixFmtDescriptor *pixdesc =
- av_pix_fmt_desc_get(imgfmt2pixfmt(imgfmt));
+ enum AVPixelFormat pixfmt = imgfmt2pixfmt(imgfmt);
+ const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(pixfmt);
// FFmpeg does not provide a flag for XYZ, so this is the best we can do.
if (pixdesc && strncmp(pixdesc->name, "xyz", 3) == 0)
@@ -331,6 +331,9 @@ enum mp_csp mp_imgfmt_get_forced_csp(int imgfmt)
if (pixdesc && (pixdesc->flags & AV_PIX_FMT_FLAG_RGB))
return MP_CSP_RGB;
+ if (pixfmt == AV_PIX_FMT_PAL8 || pixfmt == AV_PIX_FMT_MONOBLACK)
+ return MP_CSP_RGB;
+
return MP_CSP_AUTO;
}
@@ -342,7 +345,7 @@ enum mp_component_type mp_imgfmt_get_component_type(int imgfmt)
if (!pixdesc)
return MP_COMPONENT_TYPE_UNKNOWN;
-#ifdef AV_PIX_FMT_FLAG_FLOAT
+#if LIBAVUTIL_VERSION_MICRO >= 100
if (pixdesc->flags & AV_PIX_FMT_FLAG_FLOAT)
return MP_COMPONENT_TYPE_FLOAT;
#endif
@@ -438,12 +441,9 @@ bool mp_get_regular_imgfmt(struct mp_regular_imgfmt *dst, int imgfmt)
res.chroma_w = 1 << pixdesc->log2_chroma_w;
res.chroma_h = 1 << pixdesc->log2_chroma_h;
-#ifdef AV_PIX_FMT_FLAG_BAYER
+#if LIBAVUTIL_VERSION_MICRO >= 100
if (pixdesc->flags & AV_PIX_FMT_FLAG_BAYER)
return false; // it's satan himself
-#else
- if (strncmp(pixdesc->name, "bayer_", 6) == 0)
- return false; // it's satan himself
#endif
if (!validate_regular_imgfmt(&res))
diff --git a/video/img_format.h b/video/img_format.h
index 7f0330d..123c8ba 100644
--- a/video/img_format.h
+++ b/video/img_format.h
@@ -203,6 +203,8 @@ enum mp_imgfmt {
IMGFMT_DXVA2, // IDirect3DSurface9 (NV12/P010/P016)
IMGFMT_MMAL, // MMAL_BUFFER_HEADER_T
IMGFMT_VIDEOTOOLBOX, // CVPixelBufferRef
+ IMGFMT_MEDIACODEC, // AVMediaCodecBuffer
+ IMGFMT_DRMPRIME, // AVDRMFrameDescriptor
IMGFMT_CUDA, // CUDA Buffer
// Generic pass-through of AV_PIX_FMT_*. Used for formats which don't have
diff --git a/video/mp_image.c b/video/mp_image.c
index 7180e2b..603c596 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <limits.h>
@@ -28,20 +26,22 @@
#include <libavutil/rational.h>
#include <libavcodec/avcodec.h>
+#if LIBAVUTIL_VERSION_MICRO >= 100
+#include <libavutil/mastering_display_metadata.h>
+#endif
+
#include "mpv_talloc.h"
#include "config.h"
+#include "common/av_common.h"
#include "common/common.h"
+#include "hwdec.h"
#include "mp_image.h"
#include "sws_utils.h"
#include "fmt-conversion.h"
-#include "gpu_memcpy.h"
#include "video/filter/vf.h"
-#define HAVE_OPAQUE_REF (LIBAVUTIL_VERSION_MICRO >= 100 && \
- LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 47, 100))
-
const struct m_opt_choice_alternatives mp_spherical_names[] = {
{"auto", MP_SPHERICAL_AUTO},
{"none", MP_SPHERICAL_NONE},
@@ -77,7 +77,7 @@ static int mp_image_layout(int imgfmt, int w, int h, int stride_align,
out_plane_size[n] = out_stride[n] * alloc_h;
}
if (desc.flags & MP_IMGFLAG_PAL)
- out_plane_size[1] = MP_PALETTE_SIZE;
+ out_plane_size[1] = AVPALETTE_SIZE;
int sum = 0;
for (int n = 0; n < MP_MAX_PLANES; n++) {
@@ -472,7 +472,7 @@ static void mp_image_copy_cb(struct mp_image *dst, struct mp_image *src,
}
// Watch out for AV_PIX_FMT_FLAG_PSEUDOPAL retardation
if ((dst->fmt.flags & MP_IMGFLAG_PAL) && dst->planes[1] && src->planes[1])
- memcpy(dst->planes[1], src->planes[1], MP_PALETTE_SIZE);
+ memcpy(dst->planes[1], src->planes[1], AVPALETTE_SIZE);
}
void mp_image_copy(struct mp_image *dst, struct mp_image *src)
@@ -480,35 +480,10 @@ void mp_image_copy(struct mp_image *dst, struct mp_image *src)
mp_image_copy_cb(dst, src, memcpy);
}
-void mp_image_copy_gpu(struct mp_image *dst, struct mp_image *src)
-{
-#if HAVE_SSE4_INTRINSICS
- if (av_get_cpu_flags() & AV_CPU_FLAG_SSE4) {
- mp_image_copy_cb(dst, src, gpu_memcpy);
- return;
- }
-#endif
- mp_image_copy(dst, src);
-}
-
-// Helper, only for outputting some log info.
-void mp_check_gpu_memcpy(struct mp_log *log, bool *once)
+static enum mp_csp mp_image_params_get_forced_csp(struct mp_image_params *params)
{
- if (once) {
- if (*once)
- return;
- *once = true;
- }
-
- bool have_sse = false;
-#if HAVE_SSE4_INTRINSICS
- have_sse = av_get_cpu_flags() & AV_CPU_FLAG_SSE4;
-#endif
- if (have_sse) {
- mp_verbose(log, "Using SSE4 memcpy\n");
- } else {
- mp_warn(log, "Using fallback memcpy (slow)\n");
- }
+ int imgfmt = params->hw_subfmt ? params->hw_subfmt : params->imgfmt;
+ return mp_imgfmt_get_forced_csp(imgfmt);
}
void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
@@ -521,25 +496,19 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
dst->params.rotate = src->params.rotate;
dst->params.stereo_in = src->params.stereo_in;
dst->params.stereo_out = src->params.stereo_out;
- if (dst->w == src->w && dst->h == src->h) {
- dst->params.p_w = src->params.p_w;
- dst->params.p_h = src->params.p_h;
- }
- dst->params.color.primaries = src->params.color.primaries;
- dst->params.color.gamma = src->params.color.gamma;
- dst->params.color.sig_peak = src->params.color.sig_peak;
- dst->params.color.light = src->params.color.light;
- if ((dst->fmt.flags & MP_IMGFLAG_YUV) == (src->fmt.flags & MP_IMGFLAG_YUV)) {
- dst->params.color.space = src->params.color.space;
- dst->params.color.levels = src->params.color.levels;
- dst->params.chroma_location = src->params.chroma_location;
- }
+ dst->params.p_w = src->params.p_w;
+ dst->params.p_h = src->params.p_h;
+ dst->params.color = src->params.color;
+ dst->params.chroma_location = src->params.chroma_location;
dst->params.spherical = src->params.spherical;
- mp_image_params_guess_csp(&dst->params); // ensure colorspace consistency
+ // ensure colorspace consistency
+ if (mp_image_params_get_forced_csp(&dst->params) !=
+ mp_image_params_get_forced_csp(&src->params))
+ dst->params.color = (struct mp_colorspace){0};
if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) {
if (dst->planes[1] && src->planes[1]) {
if (mp_image_make_writeable(dst))
- memcpy(dst->planes[1], src->planes[1], MP_PALETTE_SIZE);
+ memcpy(dst->planes[1], src->planes[1], AVPALETTE_SIZE);
}
}
av_buffer_unref(&dst->icc_profile);
@@ -651,6 +620,8 @@ char *mp_image_params_to_str_buf(char *b, size_t bs,
mp_snprintf_cat(b, bs, " %s", mp_imgfmt_to_name(p->imgfmt));
if (p->hw_subfmt)
mp_snprintf_cat(b, bs, "[%s]", mp_imgfmt_to_name(p->hw_subfmt));
+ if (p->hw_flags)
+ mp_snprintf_cat(b, bs, "[0x%x]", p->hw_flags);
mp_snprintf_cat(b, bs, " %s/%s/%s/%s",
m_opt_choice_str(mp_csp_names, p->color.space),
m_opt_choice_str(mp_csp_prim_names, p->color.primaries),
@@ -722,6 +693,7 @@ bool mp_image_params_equal(const struct mp_image_params *p1,
{
return p1->imgfmt == p2->imgfmt &&
p1->hw_subfmt == p2->hw_subfmt &&
+ p1->hw_flags == p2->hw_flags &&
p1->w == p2->w && p1->h == p2->h &&
p1->p_w == p2->p_w && p1->p_h == p2->p_h &&
mp_colorspace_equal(p1->color, p2->color) &&
@@ -742,7 +714,7 @@ void mp_image_set_attributes(struct mp_image *image,
nparams.w = image->w;
nparams.h = image->h;
if (nparams.imgfmt != params->imgfmt)
- mp_image_params_guess_csp(&nparams);
+ nparams.color = (struct mp_colorspace){0};
mp_image_set_params(image, &nparams);
}
@@ -751,12 +723,7 @@ void mp_image_set_attributes(struct mp_image *image,
// the colorspace as implied by the pixel format.
void mp_image_params_guess_csp(struct mp_image_params *params)
{
- int imgfmt = params->hw_subfmt ? params->hw_subfmt : params->imgfmt;
- struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(imgfmt);
- if (!fmt.id)
- return;
-
- enum mp_csp forced_csp = mp_imgfmt_get_forced_csp(imgfmt);
+ enum mp_csp forced_csp = mp_image_params_get_forced_csp(params);
if (forced_csp == MP_CSP_AUTO) { // YUV/other
if (params->color.space != MP_CSP_BT_601 &&
params->color.space != MP_CSP_BT_709 &&
@@ -839,6 +806,13 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
}
}
+ if (params->chroma_location == MP_CHROMA_AUTO) {
+ if (params->color.levels == MP_CSP_LEVELS_TV)
+ params->chroma_location = MP_CHROMA_LEFT;
+ if (params->color.levels == MP_CSP_LEVELS_PC)
+ params->chroma_location = MP_CHROMA_CENTER;
+ }
+
if (params->color.light == MP_CSP_LIGHT_AUTO) {
// HLG is always scene-referred (using its own OOTF), everything else
// we assume is display-refered by default.
@@ -850,11 +824,17 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
}
}
-// Copy properties and data of the AVFrame into the mp_image, without taking
-// care of memory management issues.
-static void mp_image_copy_fields_from_av_frame(struct mp_image *dst,
- struct AVFrame *src)
+// Create a new mp_image reference to av_frame.
+struct mp_image *mp_image_from_av_frame(struct AVFrame *src)
{
+ struct mp_image *dst = &(struct mp_image){0};
+ AVFrameSideData *sd;
+
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ dst->bufs[p] = src->buf[p];
+
+ dst->hwctx = src->hw_frames_ctx;
+
mp_image_setfmt(dst, pixfmt2imgfmt(src->format));
mp_image_set_size(dst, src->width, src->height);
@@ -876,11 +856,6 @@ static void mp_image_copy_fields_from_av_frame(struct mp_image *dst,
if (src->repeat_pict == 1)
dst->fields |= MP_IMGFIELD_REPEAT_FIRST;
- if (src->hw_frames_ctx) {
- AVHWFramesContext *fctx = (void *)src->hw_frames_ctx->data;
- dst->params.hw_subfmt = pixfmt2imgfmt(fctx->sw_format);
- }
-
dst->params.color = (struct mp_colorspace){
.space = avcol_spc_to_mp_csp(src->colorspace),
.levels = avcol_range_to_mp_csp_levels(src->color_range),
@@ -890,21 +865,66 @@ static void mp_image_copy_fields_from_av_frame(struct mp_image *dst,
dst->params.chroma_location = avchroma_location_to_mp(src->chroma_location);
-#if HAVE_OPAQUE_REF
if (src->opaque_ref) {
struct mp_image_params *p = (void *)src->opaque_ref->data;
dst->params.rotate = p->rotate;
dst->params.stereo_in = p->stereo_in;
dst->params.stereo_out = p->stereo_out;
}
+
+#if LIBAVUTIL_VERSION_MICRO >= 100
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_ICC_PROFILE);
+ if (sd)
+ dst->icc_profile = av_buffer_ref(sd->buf);
+
+ // Get the content light metadata if available
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+ if (sd) {
+ AVContentLightMetadata *clm = (AVContentLightMetadata *)sd->data;
+ dst->params.color.sig_peak = clm->MaxCLL / MP_REF_WHITE;
+ }
+
+ // Otherwise, try getting the mastering metadata if available
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+ if (!dst->params.color.sig_peak && sd) {
+ AVMasteringDisplayMetadata *mdm = (AVMasteringDisplayMetadata *)sd->data;
+ if (mdm->has_luminance)
+ dst->params.color.sig_peak = av_q2d(mdm->max_luminance) / MP_REF_WHITE;
+ }
#endif
+
+ if (dst->hwctx) {
+ AVHWFramesContext *fctx = (void *)dst->hwctx->data;
+ dst->params.hw_subfmt = pixfmt2imgfmt(fctx->sw_format);
+ const struct hwcontext_fns *fns =
+ hwdec_get_hwcontext_fns(fctx->device_ctx->type);
+ if (fns && fns->complete_image_params)
+ fns->complete_image_params(dst);
+ }
+
+ return mp_image_new_ref(dst);
}
-// Copy properties and data of the mp_image into the AVFrame, without taking
-// care of memory management issues.
-static void mp_image_copy_fields_to_av_frame(struct AVFrame *dst,
- struct mp_image *src)
+
+// Convert the mp_image reference to a AVFrame reference.
+struct AVFrame *mp_image_to_av_frame(struct mp_image *src)
{
+ struct mp_image *new_ref = mp_image_new_ref(src);
+ AVFrame *dst = av_frame_alloc();
+ if (!dst || !new_ref) {
+ talloc_free(new_ref);
+ av_frame_free(&dst);
+ return NULL;
+ }
+
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ dst->buf[p] = new_ref->bufs[p];
+ new_ref->bufs[p] = NULL;
+ }
+
+ dst->hw_frames_ctx = new_ref->hwctx;
+ new_ref->hwctx = NULL;
+
dst->format = imgfmt2pixfmt(src->imgfmt);
dst->width = src->w;
dst->height = src->h;
@@ -934,45 +954,26 @@ static void mp_image_copy_fields_to_av_frame(struct AVFrame *dst,
dst->chroma_location = mp_chroma_location_to_av(src->params.chroma_location);
-#if HAVE_OPAQUE_REF
- av_buffer_unref(&dst->opaque_ref);
dst->opaque_ref = av_buffer_alloc(sizeof(struct mp_image_params));
if (!dst->opaque_ref)
abort();
*(struct mp_image_params *)dst->opaque_ref->data = src->params;
-#endif
-}
-
-// Create a new mp_image reference to av_frame.
-struct mp_image *mp_image_from_av_frame(struct AVFrame *av_frame)
-{
- struct mp_image t = {0};
- mp_image_copy_fields_from_av_frame(&t, av_frame);
- for (int p = 0; p < MP_MAX_PLANES; p++)
- t.bufs[p] = av_frame->buf[p];
- t.hwctx = av_frame->hw_frames_ctx;
- return mp_image_new_ref(&t);
-}
-// Convert the mp_image reference to a AVFrame reference.
-struct AVFrame *mp_image_to_av_frame(struct mp_image *img)
-{
- struct mp_image *new_ref = mp_image_new_ref(img);
- AVFrame *frame = av_frame_alloc();
- if (!frame || !new_ref) {
- talloc_free(new_ref);
- av_frame_free(&frame);
- return NULL;
+#if LIBAVUTIL_VERSION_MICRO >= 100
+ if (src->icc_profile) {
+ AVFrameSideData *sd =
+ ffmpeg_garbage(dst, AV_FRAME_DATA_ICC_PROFILE, new_ref->icc_profile);
+ if (!sd)
+ abort();
+ new_ref->icc_profile = NULL;
}
- mp_image_copy_fields_to_av_frame(frame, new_ref);
- for (int p = 0; p < MP_MAX_PLANES; p++)
- frame->buf[p] = new_ref->bufs[p];
- frame->hw_frames_ctx = new_ref->hwctx;
- *new_ref = (struct mp_image){0};
+#endif
+
talloc_free(new_ref);
- if (frame->format == AV_PIX_FMT_NONE)
- av_frame_free(&frame);
- return frame;
+
+ if (dst->format == AV_PIX_FMT_NONE)
+ av_frame_free(&dst);
+ return dst;
}
// Same as mp_image_to_av_frame(), but unref img. (It does so even on failure.)
diff --git a/video/mp_image.h b/video/mp_image.h
index 53b25c5..5591b2f 100644
--- a/video/mp_image.h
+++ b/video/mp_image.h
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPLAYER_MP_IMAGE_H
@@ -30,8 +28,6 @@
#include "csputils.h"
#include "video/img_format.h"
-#define MP_PALETTE_SIZE (256 * 4)
-
#define MP_IMGFIELD_TOP_FIRST 0x02
#define MP_IMGFIELD_REPEAT_FIRST 0x04
#define MP_IMGFIELD_INTERLACED 0x20
@@ -50,12 +46,18 @@ struct mp_spherical_params {
float ref_angles[3]; // yaw/pitch/roll, refer to AVSphericalMapping
};
+enum mp_image_hw_flags {
+ MP_IMAGE_HW_FLAG_OPAQUE = 1, // an opaque hw format is used - the exact
+ // format is subject to hwctx internals
+};
+
// Describes image parameters that usually stay constant.
// New fields can be added in the future. Code changing the parameters should
// usually copy the whole struct, so that fields added later will be preserved.
struct mp_image_params {
enum mp_imgfmt imgfmt; // pixel format
enum mp_imgfmt hw_subfmt; // underlying format for some hwaccel pixfmts
+ unsigned hw_flags; // bit mask of mp_image_hw_flags
int w, h; // image dimensions
int p_w, p_h; // define pixel aspect ratio (undefined: 0/0)
struct mp_colorspace color;
@@ -129,7 +131,6 @@ struct mp_image *mp_image_from_buffer(int imgfmt, int w, int h, int stride_align
struct mp_image *mp_image_alloc(int fmt, int w, int h);
void mp_image_copy(struct mp_image *dmpi, struct mp_image *mpi);
-void mp_image_copy_gpu(struct mp_image *dst, struct mp_image *src);
void mp_image_copy_attributes(struct mp_image *dmpi, struct mp_image *mpi);
struct mp_image *mp_image_new_copy(struct mp_image *img);
struct mp_image *mp_image_new_ref(struct mp_image *img);
@@ -185,6 +186,4 @@ void memcpy_pic(void *dst, const void *src, int bytesPerLine, int height,
void memset_pic(void *dst, int fill, int bytesPerLine, int height, int stride);
void memset16_pic(void *dst, int fill, int unitsPerLine, int height, int stride);
-void mp_check_gpu_memcpy(struct mp_log *log, bool *once);
-
#endif /* MPLAYER_MP_IMAGE_H */
diff --git a/video/mp_image_pool.c b/video/mp_image_pool.c
index 9a848af..809b4f3 100644
--- a/video/mp_image_pool.c
+++ b/video/mp_image_pool.c
@@ -254,8 +254,7 @@ void mp_image_pool_set_lru(struct mp_image_pool *pool)
// Copies the contents of the HW surface img to system memory and retuns it.
// If swpool is not NULL, it's used to allocate the target image.
-// img must be a hw surface with a AVHWFramesContext attached. If not, you
-// must use the legacy mp_hwdec_ctx.download_image.
+// img must be a hw surface with a AVHWFramesContext attached.
// The returned image is cropped as needed.
// Returns NULL on failure.
struct mp_image *mp_image_hw_download(struct mp_image *src,
@@ -298,9 +297,9 @@ struct mp_image *mp_image_hw_download(struct mp_image *src,
}
int res = av_hwframe_transfer_data(dstav, srcav, 0);
- av_frame_unref(srcav);
+ av_frame_free(&srcav);
dst = mp_image_from_av_frame(dstav);
- av_frame_unref(dstav);
+ av_frame_free(&dstav);
if (res >= 0 && dst) {
mp_image_set_size(dst, src->w, src->h);
mp_image_copy_attributes(dst, src);
@@ -309,3 +308,37 @@ struct mp_image *mp_image_hw_download(struct mp_image *src,
}
return dst;
}
+
+bool mp_image_hw_upload(struct mp_image *hw_img, struct mp_image *src)
+{
+ if (hw_img->w != src->w || hw_img->h != src->h)
+ return false;
+
+ if (!hw_img->hwctx || src->hwctx)
+ return false;
+
+ bool ok = false;
+ AVFrame *dstav = NULL;
+ AVFrame *srcav = NULL;
+
+ // This means the destination image will not be "writable", which would be
+ // a pain if Libav enforced this - fortunately it doesn't care. We can
+ // transfer data to it even if there are multiple refs.
+ dstav = mp_image_to_av_frame(hw_img);
+ if (!dstav)
+ goto done;
+
+ srcav = mp_image_to_av_frame(src);
+ if (!srcav)
+ goto done;
+
+ ok = av_hwframe_transfer_data(dstav, srcav, 0) >= 0;
+
+done:
+ av_frame_unref(srcav);
+ av_frame_unref(dstav);
+
+ if (ok)
+ mp_image_copy_attributes(hw_img, src);
+ return ok;
+}
diff --git a/video/mp_image_pool.h b/video/mp_image_pool.h
index 95e4ae5..5f570bc 100644
--- a/video/mp_image_pool.h
+++ b/video/mp_image_pool.h
@@ -29,4 +29,6 @@ bool mp_image_pool_make_writeable(struct mp_image_pool *pool,
struct mp_image *mp_image_hw_download(struct mp_image *img,
struct mp_image_pool *swpool);
+bool mp_image_hw_upload(struct mp_image *hw_img, struct mp_image *src);
+
#endif
diff --git a/video/out/cocoa/window.m b/video/out/cocoa/window.m
index 6d464a1..2feaab9 100644
--- a/video/out/cocoa/window.m
+++ b/video/out/cocoa/window.m
@@ -386,8 +386,11 @@
- (NSRect)constrainFrameRect:(NSRect)nf toScreen:(NSScreen *)screen
{
- if (_is_animating && ![self.adapter isInFullScreenMode])
+ if ((_is_animating && ![self.adapter isInFullScreenMode]) ||
+ (!_is_animating && [self.adapter isInFullScreenMode]))
+ {
return nf;
+ }
screen = screen ?: self.screen ?: [NSScreen mainScreen];
NSRect of = [self frame];
diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c
new file mode 100644
index 0000000..b02d2e8
--- /dev/null
+++ b/video/out/d3d11/context.c
@@ -0,0 +1,244 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "osdep/windows_utils.h"
+
+#include "video/out/gpu/context.h"
+#include "video/out/gpu/d3d11_helpers.h"
+#include "video/out/gpu/spirv.h"
+#include "video/out/w32_common.h"
+#include "ra_d3d11.h"
+
+struct d3d11_opts {
+ int feature_level;
+ int warp;
+ int flip;
+ int sync_interval;
+};
+
+#define OPT_BASE_STRUCT struct d3d11_opts
+const struct m_sub_options d3d11_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_CHOICE("d3d11-warp", warp, 0,
+ ({"auto", -1},
+ {"no", 0},
+ {"yes", 1})),
+ OPT_CHOICE("d3d11-feature-level", feature_level, 0,
+ ({"12_1", D3D_FEATURE_LEVEL_12_1},
+ {"12_0", D3D_FEATURE_LEVEL_12_0},
+ {"11_1", D3D_FEATURE_LEVEL_11_1},
+ {"11_0", D3D_FEATURE_LEVEL_11_0},
+ {"10_1", D3D_FEATURE_LEVEL_10_1},
+ {"10_0", D3D_FEATURE_LEVEL_10_0},
+ {"9_3", D3D_FEATURE_LEVEL_9_3},
+ {"9_2", D3D_FEATURE_LEVEL_9_2},
+ {"9_1", D3D_FEATURE_LEVEL_9_1})),
+ OPT_FLAG("d3d11-flip", flip, 0),
+ OPT_INTRANGE("d3d11-sync-interval", sync_interval, 0, 0, 4),
+ {0}
+ },
+ .defaults = &(const struct d3d11_opts) {
+ .feature_level = D3D_FEATURE_LEVEL_12_1,
+ .warp = -1,
+ .flip = 1,
+ .sync_interval = 1,
+ },
+ .size = sizeof(struct d3d11_opts)
+};
+
+struct priv {
+ struct d3d11_opts *opts;
+
+ struct ra_tex *backbuffer;
+ ID3D11Device *device;
+ IDXGISwapChain *swapchain;
+};
+
+static struct mp_image *d3d11_screenshot(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->ctx->priv;
+ if (!p->swapchain)
+ return NULL;
+ return mp_d3d11_screenshot(p->swapchain);
+}
+
+static struct ra_tex *get_backbuffer(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ID3D11Texture2D *backbuffer = NULL;
+ struct ra_tex *tex = NULL;
+ HRESULT hr;
+
+ hr = IDXGISwapChain_GetBuffer(p->swapchain, 0, &IID_ID3D11Texture2D,
+ (void**)&backbuffer);
+ if (FAILED(hr)) {
+ MP_ERR(ctx, "Couldn't get swapchain image\n");
+ goto done;
+ }
+
+ tex = ra_d3d11_wrap_tex(ctx->ra, (ID3D11Resource *)backbuffer);
+done:
+ SAFE_RELEASE(backbuffer);
+ return tex;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ HRESULT hr;
+
+ ra_tex_free(ctx->ra, &p->backbuffer);
+
+ hr = IDXGISwapChain_ResizeBuffers(p->swapchain, 0, ctx->vo->dwidth,
+ ctx->vo->dheight, DXGI_FORMAT_UNKNOWN, 0);
+ if (FAILED(hr)) {
+ MP_FATAL(ctx, "Couldn't resize swapchain: %s\n", mp_HRESULT_to_str(hr));
+ return false;
+ }
+
+ p->backbuffer = get_backbuffer(ctx);
+
+ return true;
+}
+
+static bool d3d11_reconfig(struct ra_ctx *ctx)
+{
+ vo_w32_config(ctx->vo);
+ return resize(ctx);
+}
+
+static int d3d11_color_depth(struct ra_swapchain *sw)
+{
+ return 8;
+}
+
+static bool d3d11_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
+{
+ struct priv *p = sw->priv;
+ *out_fbo = (struct ra_fbo) {
+ .tex = p->backbuffer,
+ .flip = false,
+ };
+ return true;
+}
+
+static bool d3d11_submit_frame(struct ra_swapchain *sw,
+ const struct vo_frame *frame)
+{
+ ra_d3d11_flush(sw->ctx->ra);
+ return true;
+}
+
+static void d3d11_swap_buffers(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+ IDXGISwapChain_Present(p->swapchain, p->opts->sync_interval, 0);
+}
+
+static int d3d11_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_w32_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+static void d3d11_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_tex_free(ctx->ra, &p->backbuffer);
+ SAFE_RELEASE(p->swapchain);
+ vo_w32_uninit(ctx->vo);
+ SAFE_RELEASE(p->device);
+
+ // Destory the RA last to prevent objects we hold from showing up in D3D's
+ // leak checker
+ ctx->ra->fns->destroy(ctx->ra);
+}
+
+static const struct ra_swapchain_fns d3d11_swapchain = {
+ .color_depth = d3d11_color_depth,
+ .screenshot = d3d11_screenshot,
+ .start_frame = d3d11_start_frame,
+ .submit_frame = d3d11_submit_frame,
+ .swap_buffers = d3d11_swap_buffers,
+};
+
+static bool d3d11_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ p->opts = mp_get_config_group(ctx, ctx->global, &d3d11_conf);
+
+ struct ra_swapchain *sw = ctx->swapchain = talloc_zero(ctx, struct ra_swapchain);
+ sw->priv = p;
+ sw->ctx = ctx;
+ sw->fns = &d3d11_swapchain;
+
+ struct d3d11_device_opts dopts = {
+ .debug = ctx->opts.debug,
+ .allow_warp = p->opts->warp != 0,
+ .force_warp = p->opts->warp == 1,
+ .max_feature_level = p->opts->feature_level,
+ .max_frame_latency = ctx->opts.swapchain_depth,
+ };
+ if (!mp_d3d11_create_present_device(ctx->log, &dopts, &p->device))
+ goto error;
+
+ if (!spirv_compiler_init(ctx))
+ goto error;
+ ctx->ra = ra_d3d11_create(p->device, ctx->log, ctx->spirv);
+ if (!ctx->ra)
+ goto error;
+
+ if (!vo_w32_init(ctx->vo))
+ goto error;
+
+ struct d3d11_swapchain_opts scopts = {
+ .window = vo_w32_hwnd(ctx->vo),
+ .width = ctx->vo->dwidth,
+ .height = ctx->vo->dheight,
+ .flip = p->opts->flip,
+ // Add one frame for the backbuffer and one frame of "slack" to reduce
+ // contention with the window manager when acquiring the backbuffer
+ .length = ctx->opts.swapchain_depth + 2,
+ .usage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
+ };
+ if (!mp_d3d11_create_swapchain(p->device, ctx->log, &scopts, &p->swapchain))
+ goto error;
+
+ p->backbuffer = get_backbuffer(ctx);
+
+ return true;
+
+error:
+ d3d11_uninit(ctx);
+ return false;
+}
+
+const struct ra_ctx_fns ra_ctx_d3d11 = {
+ .type = "d3d11",
+ .name = "d3d11",
+ .reconfig = d3d11_reconfig,
+ .control = d3d11_control,
+ .init = d3d11_init,
+ .uninit = d3d11_uninit,
+};
diff --git a/video/out/d3d11/hwdec_d3d11va.c b/video/out/d3d11/hwdec_d3d11va.c
new file mode 100644
index 0000000..d83fdc5
--- /dev/null
+++ b/video/out/d3d11/hwdec_d3d11va.c
@@ -0,0 +1,249 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <windows.h>
+#include <d3d11.h>
+#include <d3d11_1.h>
+
+#include "config.h"
+
+#include "common/common.h"
+#include "options/m_config.h"
+#include "osdep/windows_utils.h"
+#include "video/hwdec.h"
+#include "video/d3d.h"
+#include "video/out/d3d11/ra_d3d11.h"
+#include "video/out/gpu/hwdec.h"
+
+struct d3d11va_opts {
+ int zero_copy;
+};
+
+#define OPT_BASE_STRUCT struct d3d11va_opts
+const struct m_sub_options d3d11va_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_FLAG("d3d11va-zero-copy", zero_copy, 0),
+ {0}
+ },
+ .defaults = &(const struct d3d11va_opts) {
+ .zero_copy = 0,
+ },
+ .size = sizeof(struct d3d11va_opts)
+};
+
+struct priv_owner {
+ struct d3d11va_opts *opts;
+
+ struct mp_hwdec_ctx hwctx;
+ ID3D11Device *device;
+ ID3D11Device1 *device1;
+};
+
+struct priv {
+ // 1-copy path
+ ID3D11DeviceContext1 *ctx;
+ ID3D11Texture2D *copy_tex;
+
+ // zero-copy path
+ int num_planes;
+ const struct ra_format *fmt[4];
+};
+
+static void uninit(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+ hwdec_devices_remove(hw->devs, &p->hwctx);
+ SAFE_RELEASE(p->device);
+ SAFE_RELEASE(p->device1);
+}
+
+static int init(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+ HRESULT hr;
+
+ if (!ra_is_d3d11(hw->ra))
+ return -1;
+ p->device = ra_d3d11_get_device(hw->ra);
+ if (!p->device)
+ return -1;
+
+ p->opts = mp_get_config_group(hw->priv, hw->global, &d3d11va_conf);
+
+ // D3D11VA requires Direct3D 11.1, so this should always succeed
+ hr = ID3D11Device_QueryInterface(p->device, &IID_ID3D11Device1,
+ (void**)&p->device1);
+ if (FAILED(hr)) {
+ MP_ERR(hw, "Failed to get D3D11.1 interface: %s\n",
+ mp_HRESULT_to_str(hr));
+ return -1;
+ }
+
+ ID3D10Multithread *multithread;
+ hr = ID3D11Device_QueryInterface(p->device, &IID_ID3D10Multithread,
+ (void **)&multithread);
+ if (FAILED(hr)) {
+ MP_ERR(hw, "Failed to get Multithread interface: %s\n",
+ mp_HRESULT_to_str(hr));
+ return -1;
+ }
+ ID3D10Multithread_SetMultithreadProtected(multithread, TRUE);
+ ID3D10Multithread_Release(multithread);
+
+ p->hwctx = (struct mp_hwdec_ctx){
+ .driver_name = hw->driver->name,
+ .av_device_ref = d3d11_wrap_device_ref(p->device),
+ };
+ hwdec_devices_add(hw->devs, &p->hwctx);
+ return 0;
+}
+
+static void mapper_uninit(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ for (int i = 0; i < 4; i++)
+ ra_tex_free(mapper->ra, &mapper->tex[i]);
+ SAFE_RELEASE(p->copy_tex);
+ SAFE_RELEASE(p->ctx);
+}
+
+static int mapper_init(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *o = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+ HRESULT hr;
+
+ mapper->dst_params = mapper->src_params;
+ mapper->dst_params.imgfmt = mapper->src_params.hw_subfmt;
+ mapper->dst_params.hw_subfmt = 0;
+
+ struct ra_imgfmt_desc desc = {0};
+
+ if (!ra_get_imgfmt_desc(mapper->ra, mapper->dst_params.imgfmt, &desc))
+ return -1;
+
+ if (o->opts->zero_copy) {
+ // In the zero-copy path, we create the ra_tex objects in the map
+ // operation, so we just need to store the format of each plane
+ p->num_planes = desc.num_planes;
+ for (int i = 0; i < desc.num_planes; i++)
+ p->fmt[i] = desc.planes[i];
+ } else {
+ struct mp_image layout = {0};
+ mp_image_set_params(&layout, &mapper->dst_params);
+
+ DXGI_FORMAT copy_fmt;
+ switch (mapper->dst_params.imgfmt) {
+ case IMGFMT_NV12: copy_fmt = DXGI_FORMAT_NV12; break;
+ case IMGFMT_P010: copy_fmt = DXGI_FORMAT_P010; break;
+ default: return -1;
+ }
+
+ D3D11_TEXTURE2D_DESC copy_desc = {
+ .Width = mapper->dst_params.w,
+ .Height = mapper->dst_params.h,
+ .MipLevels = 1,
+ .ArraySize = 1,
+ .SampleDesc.Count = 1,
+ .Format = copy_fmt,
+ .BindFlags = D3D11_BIND_SHADER_RESOURCE,
+ };
+ hr = ID3D11Device_CreateTexture2D(o->device, &copy_desc, NULL,
+ &p->copy_tex);
+ if (FAILED(hr)) {
+ MP_FATAL(mapper, "Could not create shader resource texture\n");
+ return -1;
+ }
+
+ for (int i = 0; i < desc.num_planes; i++) {
+ mapper->tex[i] = ra_d3d11_wrap_tex_video(mapper->ra, p->copy_tex,
+ mp_image_plane_w(&layout, i), mp_image_plane_h(&layout, i), 0,
+ desc.planes[i]);
+ if (!mapper->tex[i]) {
+ MP_FATAL(mapper, "Could not create RA texture view\n");
+ return -1;
+ }
+ }
+
+ // A ref to the immediate context is needed for CopySubresourceRegion
+ ID3D11Device1_GetImmediateContext1(o->device1, &p->ctx);
+ }
+
+ return 0;
+}
+
+static int mapper_map(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ ID3D11Texture2D *tex = (void *)mapper->src->planes[0];
+ int subresource = (intptr_t)mapper->src->planes[1];
+
+ if (p->copy_tex) {
+ ID3D11DeviceContext1_CopySubresourceRegion1(p->ctx,
+ (ID3D11Resource *)p->copy_tex, 0, 0, 0, 0,
+ (ID3D11Resource *)tex, subresource, (&(D3D11_BOX) {
+ .left = 0,
+ .top = 0,
+ .front = 0,
+ .right = mapper->dst_params.w,
+ .bottom = mapper->dst_params.h,
+ .back = 1,
+ }), D3D11_COPY_DISCARD);
+ } else {
+ D3D11_TEXTURE2D_DESC desc2d;
+ ID3D11Texture2D_GetDesc(tex, &desc2d);
+
+ for (int i = 0; i < p->num_planes; i++) {
+ // The video decode texture may include padding, so the size of the
+ // ra_tex needs to be determined by the actual size of the Tex2D
+ bool chroma = i >= 1;
+ int w = desc2d.Width / (chroma ? 2 : 1);
+ int h = desc2d.Height / (chroma ? 2 : 1);
+
+ mapper->tex[i] = ra_d3d11_wrap_tex_video(mapper->ra, tex,
+ w, h, subresource, p->fmt[i]);
+ if (!mapper->tex[i])
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void mapper_unmap(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ if (p->copy_tex)
+ return;
+ for (int i = 0; i < 4; i++)
+ ra_tex_free(mapper->ra, &mapper->tex[i]);
+}
+
+const struct ra_hwdec_driver ra_hwdec_d3d11va = {
+ .name = "d3d11va",
+ .priv_size = sizeof(struct priv_owner),
+ .imgfmts = {IMGFMT_D3D11VA, IMGFMT_D3D11NV12, 0},
+ .init = init,
+ .uninit = uninit,
+ .mapper = &(const struct ra_hwdec_mapper_driver){
+ .priv_size = sizeof(struct priv),
+ .init = mapper_init,
+ .uninit = mapper_uninit,
+ .map = mapper_map,
+ .unmap = mapper_unmap,
+ },
+};
diff --git a/video/out/d3d11/ra_d3d11.c b/video/out/d3d11/ra_d3d11.c
new file mode 100644
index 0000000..63dc5b9
--- /dev/null
+++ b/video/out/d3d11/ra_d3d11.c
@@ -0,0 +1,2371 @@
+#include <windows.h>
+#include <versionhelpers.h>
+#include <d3d11_1.h>
+#include <d3d11sdklayers.h>
+#include <dxgi1_2.h>
+#include <d3dcompiler.h>
+#include <crossc.h>
+
+#include "common/msg.h"
+#include "osdep/io.h"
+#include "osdep/subprocess.h"
+#include "osdep/timer.h"
+#include "osdep/windows_utils.h"
+#include "video/out/gpu/spirv.h"
+#include "video/out/gpu/utils.h"
+
+#include "ra_d3d11.h"
+
+#ifndef D3D11_1_UAV_SLOT_COUNT
+#define D3D11_1_UAV_SLOT_COUNT (64)
+#endif
+
+struct dll_version {
+ uint16_t major;
+ uint16_t minor;
+ uint16_t build;
+ uint16_t revision;
+};
+
+struct ra_d3d11 {
+ struct spirv_compiler *spirv;
+
+ ID3D11Device *dev;
+ ID3D11Device1 *dev1;
+ ID3D11DeviceContext *ctx;
+ ID3D11DeviceContext1 *ctx1;
+ pD3DCompile D3DCompile;
+
+ struct dll_version d3d_compiler_ver;
+
+ // Debug interfaces (--gpu-debug)
+ ID3D11Debug *debug;
+ ID3D11InfoQueue *iqueue;
+
+ // Device capabilities
+ D3D_FEATURE_LEVEL fl;
+ bool has_clear_view;
+ bool has_timestamp_queries;
+ int max_uavs;
+
+ // Streaming dynamic vertex buffer, which is used for all renderpasses
+ ID3D11Buffer *vbuf;
+ size_t vbuf_size;
+ size_t vbuf_used;
+
+ // clear() renderpass resources (only used when has_clear_view is false)
+ ID3D11PixelShader *clear_ps;
+ ID3D11VertexShader *clear_vs;
+ ID3D11InputLayout *clear_layout;
+ ID3D11Buffer *clear_vbuf;
+ ID3D11Buffer *clear_cbuf;
+
+ // blit() renderpass resources
+ ID3D11PixelShader *blit_float_ps;
+ ID3D11VertexShader *blit_vs;
+ ID3D11InputLayout *blit_layout;
+ ID3D11Buffer *blit_vbuf;
+ ID3D11SamplerState *blit_sampler;
+};
+
+struct d3d_tex {
+ // res mirrors one of tex1d, tex2d or tex3d for convenience. It does not
+ // hold an additional reference to the texture object.
+ ID3D11Resource *res;
+
+ ID3D11Texture1D *tex1d;
+ ID3D11Texture2D *tex2d;
+ ID3D11Texture3D *tex3d;
+ int array_slice;
+
+ ID3D11ShaderResourceView *srv;
+ ID3D11RenderTargetView *rtv;
+ ID3D11UnorderedAccessView *uav;
+ ID3D11SamplerState *sampler;
+};
+
+struct d3d_buf {
+ ID3D11Buffer *buf;
+ ID3D11Buffer *staging;
+ ID3D11UnorderedAccessView *uav;
+ void *data; // Data for mapped staging texture
+};
+
+struct d3d_rpass {
+ ID3D11PixelShader *ps;
+ ID3D11VertexShader *vs;
+ ID3D11ComputeShader *cs;
+ ID3D11InputLayout *layout;
+ ID3D11BlendState *bstate;
+};
+
+struct d3d_timer {
+ ID3D11Query *ts_start;
+ ID3D11Query *ts_end;
+ ID3D11Query *disjoint;
+ uint64_t result; // Latches the result from the previous use of the timer
+};
+
+struct d3d_fmt {
+ const char *name;
+ int components;
+ int bytes;
+ int bits[4];
+ DXGI_FORMAT fmt;
+ enum ra_ctype ctype;
+ bool unordered;
+};
+
+static const char clear_vs[] = "\
+float4 main(float2 pos : POSITION) : SV_Position\n\
+{\n\
+ return float4(pos, 0.0, 1.0);\n\
+}\n\
+";
+
+static const char clear_ps[] = "\
+cbuffer ps_cbuf : register(b0) {\n\
+ float4 color : packoffset(c0);\n\
+}\n\
+\n\
+float4 main(float4 pos : SV_Position) : SV_Target\n\
+{\n\
+ return color;\n\
+}\n\
+";
+
+struct blit_vert {
+ float x, y, u, v;
+};
+
+static const char blit_vs[] = "\
+void main(float2 pos : POSITION, float2 coord : TEXCOORD0,\n\
+ out float4 out_pos : SV_Position, out float2 out_coord : TEXCOORD0)\n\
+{\n\
+ out_pos = float4(pos, 0.0, 1.0);\n\
+ out_coord = coord;\n\
+}\n\
+";
+
+static const char blit_float_ps[] = "\
+Texture2D<float4> tex : register(t0);\n\
+SamplerState samp : register(s0);\n\
+\n\
+float4 main(float4 pos : SV_Position, float2 coord : TEXCOORD0) : SV_Target\n\
+{\n\
+ return tex.Sample(samp, coord);\n\
+}\n\
+";
+
+#define DXFMT(f, t) .fmt = DXGI_FORMAT_##f##_##t, .ctype = RA_CTYPE_##t
+static struct d3d_fmt formats[] = {
+ { "r8", 1, 1, { 8}, DXFMT(R8, UNORM) },
+ { "rg8", 2, 2, { 8, 8}, DXFMT(R8G8, UNORM) },
+ { "rgba8", 4, 4, { 8, 8, 8, 8}, DXFMT(R8G8B8A8, UNORM) },
+ { "r16", 1, 2, {16}, DXFMT(R16, UNORM) },
+ { "rg16", 2, 4, {16, 16}, DXFMT(R16G16, UNORM) },
+ { "rgba16", 4, 8, {16, 16, 16, 16}, DXFMT(R16G16B16A16, UNORM) },
+
+ { "r32ui", 1, 4, {32}, DXFMT(R32, UINT) },
+ { "rg32ui", 2, 8, {32, 32}, DXFMT(R32G32, UINT) },
+ { "rgb32ui", 3, 12, {32, 32, 32}, DXFMT(R32G32B32, UINT) },
+ { "rgba32ui", 4, 16, {32, 32, 32, 32}, DXFMT(R32G32B32A32, UINT) },
+
+ { "r16hf", 1, 2, {16}, DXFMT(R16, FLOAT) },
+ { "rg16hf", 2, 4, {16, 16}, DXFMT(R16G16, FLOAT) },
+ { "rgba16hf", 4, 8, {16, 16, 16, 16}, DXFMT(R16G16B16A16, FLOAT) },
+ { "r32f", 1, 4, {32}, DXFMT(R32, FLOAT) },
+ { "rg32f", 2, 8, {32, 32}, DXFMT(R32G32, FLOAT) },
+ { "rgb32f", 3, 12, {32, 32, 32}, DXFMT(R32G32B32, FLOAT) },
+ { "rgba32f", 4, 16, {32, 32, 32, 32}, DXFMT(R32G32B32A32, FLOAT) },
+
+ { "rgb10_a2", 4, 4, {10, 10, 10, 2}, DXFMT(R10G10B10A2, UNORM) },
+ { "bgra8", 4, 4, { 8, 8, 8, 8}, DXFMT(B8G8R8A8, UNORM), .unordered = true },
+};
+
+static bool dll_version_equal(struct dll_version a, struct dll_version b)
+{
+ return a.major == b.major &&
+ a.minor == b.minor &&
+ a.build == b.build &&
+ a.revision == b.revision;
+}
+
+static DXGI_FORMAT fmt_to_dxgi(const struct ra_format *fmt)
+{
+ struct d3d_fmt *d3d = fmt->priv;
+ return d3d->fmt;
+}
+
+static void setup_formats(struct ra *ra)
+{
+ // All formats must be usable as a 2D texture
+ static const UINT sup_basic = D3D11_FORMAT_SUPPORT_TEXTURE2D;
+ // SHADER_SAMPLE indicates support for linear sampling, point always works
+ static const UINT sup_filter = D3D11_FORMAT_SUPPORT_SHADER_SAMPLE;
+ // RA requires renderable surfaces to be blendable as well
+ static const UINT sup_render = D3D11_FORMAT_SUPPORT_RENDER_TARGET |
+ D3D11_FORMAT_SUPPORT_BLENDABLE;
+
+ struct ra_d3d11 *p = ra->priv;
+ HRESULT hr;
+
+ for (int i = 0; i < MP_ARRAY_SIZE(formats); i++) {
+ struct d3d_fmt *d3dfmt = &formats[i];
+ UINT support = 0;
+ hr = ID3D11Device_CheckFormatSupport(p->dev, d3dfmt->fmt, &support);
+ if (FAILED(hr))
+ continue;
+ if ((support & sup_basic) != sup_basic)
+ continue;
+
+ struct ra_format *fmt = talloc_zero(ra, struct ra_format);
+ *fmt = (struct ra_format) {
+ .name = d3dfmt->name,
+ .priv = d3dfmt,
+ .ctype = d3dfmt->ctype,
+ .ordered = !d3dfmt->unordered,
+ .num_components = d3dfmt->components,
+ .pixel_size = d3dfmt->bytes,
+ .linear_filter = (support & sup_filter) == sup_filter,
+ .renderable = (support & sup_render) == sup_render,
+ };
+
+ if (support & D3D11_FORMAT_SUPPORT_TEXTURE1D)
+ ra->caps |= RA_CAP_TEX_1D;
+
+ for (int j = 0; j < d3dfmt->components; j++)
+ fmt->component_size[j] = fmt->component_depth[j] = d3dfmt->bits[j];
+
+ fmt->glsl_format = ra_fmt_glsl_format(fmt);
+
+ MP_TARRAY_APPEND(ra, ra->formats, ra->num_formats, fmt);
+ }
+}
+
+static bool tex_init(struct ra *ra, struct ra_tex *tex)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_tex *tex_p = tex->priv;
+ struct ra_tex_params *params = &tex->params;
+ HRESULT hr;
+
+ // A SRV is required for renderpasses and blitting, since blitting can use
+ // a renderpass internally
+ if (params->render_src || params->blit_src) {
+ // Always specify the SRV format for simplicity. This will match the
+ // texture format for textures created with tex_create, but it can be
+ // different for wrapped planar video textures.
+ D3D11_SHADER_RESOURCE_VIEW_DESC srvdesc = {
+ .Format = fmt_to_dxgi(params->format),
+ };
+ switch (params->dimensions) {
+ case 1:
+ if (tex_p->array_slice >= 0) {
+ srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
+ srvdesc.Texture1DArray.MipLevels = 1;
+ srvdesc.Texture1DArray.FirstArraySlice = tex_p->array_slice;
+ srvdesc.Texture1DArray.ArraySize = 1;
+ } else {
+ srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
+ srvdesc.Texture1D.MipLevels = 1;
+ }
+ break;
+ case 2:
+ if (tex_p->array_slice >= 0) {
+ srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
+ srvdesc.Texture2DArray.MipLevels = 1;
+ srvdesc.Texture2DArray.FirstArraySlice = tex_p->array_slice;
+ srvdesc.Texture2DArray.ArraySize = 1;
+ } else {
+ srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+ srvdesc.Texture2D.MipLevels = 1;
+ }
+ break;
+ case 3:
+ // D3D11 does not have Texture3D arrays
+ srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
+ srvdesc.Texture3D.MipLevels = 1;
+ break;
+ }
+ hr = ID3D11Device_CreateShaderResourceView(p->dev, tex_p->res, &srvdesc,
+ &tex_p->srv);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create SRV: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ }
+
+ // Samplers are required for renderpasses, but not blitting, since the blit
+ // code uses its own point sampler
+ if (params->render_src) {
+ D3D11_SAMPLER_DESC sdesc = {
+ .AddressU = D3D11_TEXTURE_ADDRESS_CLAMP,
+ .AddressV = D3D11_TEXTURE_ADDRESS_CLAMP,
+ .AddressW = D3D11_TEXTURE_ADDRESS_CLAMP,
+ .ComparisonFunc = D3D11_COMPARISON_NEVER,
+ .MinLOD = 0,
+ .MaxLOD = D3D11_FLOAT32_MAX,
+ .MaxAnisotropy = 1,
+ };
+ if (params->src_linear)
+ sdesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+ if (params->src_repeat) {
+ sdesc.AddressU = sdesc.AddressV = sdesc.AddressW =
+ D3D11_TEXTURE_ADDRESS_WRAP;
+ }
+ // The runtime pools sampler state objects internally, so we don't have
+ // to worry about resource usage when creating one for every ra_tex
+ hr = ID3D11Device_CreateSamplerState(p->dev, &sdesc, &tex_p->sampler);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create sampler: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ }
+
+ // Like SRVs, an RTV is required for renderpass output and blitting
+ if (params->render_dst || params->blit_dst) {
+ hr = ID3D11Device_CreateRenderTargetView(p->dev, tex_p->res, NULL,
+ &tex_p->rtv);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create RTV: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ }
+
+ if (p->fl >= D3D_FEATURE_LEVEL_11_0 && params->storage_dst) {
+ hr = ID3D11Device_CreateUnorderedAccessView(p->dev, tex_p->res, NULL,
+ &tex_p->uav);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create UAV: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ }
+
+ return true;
+error:
+ return false;
+}
+
+static void tex_destroy(struct ra *ra, struct ra_tex *tex)
+{
+ if (!tex)
+ return;
+ struct d3d_tex *tex_p = tex->priv;
+
+ SAFE_RELEASE(tex_p->srv);
+ SAFE_RELEASE(tex_p->rtv);
+ SAFE_RELEASE(tex_p->uav);
+ SAFE_RELEASE(tex_p->sampler);
+ SAFE_RELEASE(tex_p->res);
+ talloc_free(tex);
+}
+
+static struct ra_tex *tex_create(struct ra *ra,
+ const struct ra_tex_params *params)
+{
+ struct ra_d3d11 *p = ra->priv;
+ HRESULT hr;
+
+ struct ra_tex *tex = talloc_zero(NULL, struct ra_tex);
+ tex->params = *params;
+ tex->params.initial_data = NULL;
+
+ struct d3d_tex *tex_p = tex->priv = talloc_zero(tex, struct d3d_tex);
+ DXGI_FORMAT fmt = fmt_to_dxgi(params->format);
+
+ D3D11_SUBRESOURCE_DATA *pdata = NULL;
+ if (params->initial_data) {
+ pdata = &(D3D11_SUBRESOURCE_DATA) {
+ .pSysMem = params->initial_data,
+ .SysMemPitch = params->w * params->format->pixel_size,
+ };
+ if (params->dimensions >= 3)
+ pdata->SysMemSlicePitch = pdata->SysMemPitch * params->h;
+ }
+
+ D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
+ D3D11_BIND_FLAG bind_flags = 0;
+
+ if (params->render_src || params->blit_src)
+ bind_flags |= D3D11_BIND_SHADER_RESOURCE;
+ if (params->render_dst || params->blit_dst)
+ bind_flags |= D3D11_BIND_RENDER_TARGET;
+ if (p->fl >= D3D_FEATURE_LEVEL_11_0 && params->storage_dst)
+ bind_flags |= D3D11_BIND_UNORDERED_ACCESS;
+
+ // Apparently IMMUTABLE textures are efficient, so try to infer whether we
+ // can use one
+ if (params->initial_data && !params->render_dst && !params->storage_dst &&
+ !params->blit_dst && !params->host_mutable)
+ usage = D3D11_USAGE_IMMUTABLE;
+
+ switch (params->dimensions) {
+ case 1:;
+ D3D11_TEXTURE1D_DESC desc1d = {
+ .Width = params->w,
+ .MipLevels = 1,
+ .ArraySize = 1,
+ .Format = fmt,
+ .Usage = usage,
+ .BindFlags = bind_flags,
+ };
+ hr = ID3D11Device_CreateTexture1D(p->dev, &desc1d, pdata, &tex_p->tex1d);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create Texture1D: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ tex_p->res = (ID3D11Resource *)tex_p->tex1d;
+ break;
+ case 2:;
+ D3D11_TEXTURE2D_DESC desc2d = {
+ .Width = params->w,
+ .Height = params->h,
+ .MipLevels = 1,
+ .ArraySize = 1,
+ .SampleDesc.Count = 1,
+ .Format = fmt,
+ .Usage = usage,
+ .BindFlags = bind_flags,
+ };
+ hr = ID3D11Device_CreateTexture2D(p->dev, &desc2d, pdata, &tex_p->tex2d);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create Texture2D: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ tex_p->res = (ID3D11Resource *)tex_p->tex2d;
+ break;
+ case 3:;
+ D3D11_TEXTURE3D_DESC desc3d = {
+ .Width = params->w,
+ .Height = params->h,
+ .Depth = params->d,
+ .MipLevels = 1,
+ .Format = fmt,
+ .Usage = usage,
+ .BindFlags = bind_flags,
+ };
+ hr = ID3D11Device_CreateTexture3D(p->dev, &desc3d, pdata, &tex_p->tex3d);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create Texture3D: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ tex_p->res = (ID3D11Resource *)tex_p->tex3d;
+ break;
+ default:
+ abort();
+ }
+
+ tex_p->array_slice = -1;
+
+ if (!tex_init(ra, tex))
+ goto error;
+
+ return tex;
+
+error:
+ tex_destroy(ra, tex);
+ return NULL;
+}
+
+struct ra_tex *ra_d3d11_wrap_tex(struct ra *ra, ID3D11Resource *res)
+{
+ HRESULT hr;
+
+ struct ra_tex *tex = talloc_zero(NULL, struct ra_tex);
+ struct ra_tex_params *params = &tex->params;
+ struct d3d_tex *tex_p = tex->priv = talloc_zero(tex, struct d3d_tex);
+
+ DXGI_FORMAT fmt = DXGI_FORMAT_UNKNOWN;
+ D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
+ D3D11_BIND_FLAG bind_flags = 0;
+
+ D3D11_RESOURCE_DIMENSION type;
+ ID3D11Resource_GetType(res, &type);
+ switch (type) {
+ case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
+ hr = ID3D11Resource_QueryInterface(res, &IID_ID3D11Texture2D,
+ (void**)&tex_p->tex2d);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Resource is not a ID3D11Texture2D\n");
+ goto error;
+ }
+ tex_p->res = (ID3D11Resource *)tex_p->tex2d;
+
+ D3D11_TEXTURE2D_DESC desc2d;
+ ID3D11Texture2D_GetDesc(tex_p->tex2d, &desc2d);
+ if (desc2d.MipLevels != 1) {
+ MP_ERR(ra, "Mipmapped textures not supported for wrapping\n");
+ goto error;
+ }
+ if (desc2d.ArraySize != 1) {
+ MP_ERR(ra, "Texture arrays not supported for wrapping\n");
+ goto error;
+ }
+ if (desc2d.SampleDesc.Count != 1) {
+ MP_ERR(ra, "Multisampled textures not supported for wrapping\n");
+ goto error;
+ }
+
+ params->dimensions = 2;
+ params->w = desc2d.Width;
+ params->h = desc2d.Height;
+ params->d = 1;
+ usage = desc2d.Usage;
+ bind_flags = desc2d.BindFlags;
+ fmt = desc2d.Format;
+ break;
+ default:
+ // We could wrap Texture1D/3D as well, but keep it simple, since this
+ // function is only used for swapchain backbuffers at the moment
+ MP_ERR(ra, "Resource is not suitable to wrap\n");
+ goto error;
+ }
+
+ for (int i = 0; i < ra->num_formats; i++) {
+ DXGI_FORMAT target_fmt = fmt_to_dxgi(ra->formats[i]);
+ if (fmt == target_fmt) {
+ params->format = ra->formats[i];
+ break;
+ }
+ }
+ if (!params->format) {
+ MP_ERR(ra, "Could not find a suitable RA format for wrapped resource\n");
+ goto error;
+ }
+
+ if (bind_flags & D3D11_BIND_SHADER_RESOURCE)
+ params->render_src = params->blit_src = true;
+ if (bind_flags & D3D11_BIND_RENDER_TARGET)
+ params->render_dst = params->blit_dst = true;
+ if (bind_flags & D3D11_BIND_UNORDERED_ACCESS)
+ params->storage_dst = true;
+
+ if (usage != D3D11_USAGE_DEFAULT) {
+ MP_ERR(ra, "Resource is not D3D11_USAGE_DEFAULT\n");
+ goto error;
+ }
+
+ tex_p->array_slice = -1;
+
+ if (!tex_init(ra, tex))
+ goto error;
+
+ return tex;
+error:
+ tex_destroy(ra, tex);
+ return NULL;
+}
+
+struct ra_tex *ra_d3d11_wrap_tex_video(struct ra *ra, ID3D11Texture2D *res,
+ int w, int h, int array_slice,
+ const struct ra_format *fmt)
+{
+ struct ra_tex *tex = talloc_zero(NULL, struct ra_tex);
+ struct ra_tex_params *params = &tex->params;
+ struct d3d_tex *tex_p = tex->priv = talloc_zero(tex, struct d3d_tex);
+
+ tex_p->tex2d = res;
+ tex_p->res = (ID3D11Resource *)tex_p->tex2d;
+ ID3D11Texture2D_AddRef(res);
+
+ D3D11_TEXTURE2D_DESC desc2d;
+ ID3D11Texture2D_GetDesc(tex_p->tex2d, &desc2d);
+ if (!(desc2d.BindFlags & D3D11_BIND_SHADER_RESOURCE)) {
+ MP_ERR(ra, "Video resource is not bindable\n");
+ goto error;
+ }
+
+ params->dimensions = 2;
+ params->w = w;
+ params->h = h;
+ params->d = 1;
+ params->render_src = true;
+ params->src_linear = true;
+ // fmt can be different to the texture format for planar video textures
+ params->format = fmt;
+
+ if (desc2d.ArraySize > 1) {
+ tex_p->array_slice = array_slice;
+ } else {
+ tex_p->array_slice = -1;
+ }
+
+ if (!tex_init(ra, tex))
+ goto error;
+
+ return tex;
+error:
+ tex_destroy(ra, tex);
+ return NULL;
+}
+
+static bool tex_upload(struct ra *ra, const struct ra_tex_upload_params *params)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct ra_tex *tex = params->tex;
+ struct d3d_tex *tex_p = tex->priv;
+
+ if (!params->src) {
+ MP_ERR(ra, "Pixel buffers are not supported\n");
+ return false;
+ }
+
+ const char *src = params->src;
+ ptrdiff_t stride = tex->params.dimensions >= 2 ? tex->params.w : 0;
+ ptrdiff_t pitch = tex->params.dimensions >= 3 ? stride * tex->params.h : 0;
+ bool invalidate = true;
+ D3D11_BOX *rc = NULL;
+
+ if (tex->params.dimensions == 2) {
+ stride = params->stride;
+
+ if (params->rc && (params->rc->x0 != 0 || params->rc->y0 != 0 ||
+ params->rc->x1 != tex->params.w || params->rc->y1 != tex->params.h))
+ {
+ rc = &(D3D11_BOX) {
+ .left = params->rc->x0,
+ .top = params->rc->y0,
+ .front = 0,
+ .right = params->rc->x1,
+ .bottom = params->rc->y1,
+ .back = 1,
+ };
+ invalidate = params->invalidate;
+ }
+ }
+
+ int subresource = tex_p->array_slice >= 0 ? tex_p->array_slice : 0;
+ if (p->ctx1) {
+ ID3D11DeviceContext1_UpdateSubresource1(p->ctx1, tex_p->res,
+ subresource, rc, src, stride, pitch,
+ invalidate ? D3D11_COPY_DISCARD : 0);
+ } else {
+ ID3D11DeviceContext_UpdateSubresource(p->ctx, tex_p->res, subresource,
+ rc, src, stride, pitch);
+ }
+
+ return true;
+}
+
+static void buf_destroy(struct ra *ra, struct ra_buf *buf)
+{
+ if (!buf)
+ return;
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_buf *buf_p = buf->priv;
+
+ if (buf_p->data)
+ ID3D11DeviceContext_Unmap(p->ctx, (ID3D11Resource *)buf_p->staging, 0);
+ SAFE_RELEASE(buf_p->buf);
+ SAFE_RELEASE(buf_p->staging);
+ SAFE_RELEASE(buf_p->uav);
+ talloc_free(buf);
+}
+
+static struct ra_buf *buf_create(struct ra *ra,
+ const struct ra_buf_params *params)
+{
+ // D3D11 does not support permanent mapping or pixel buffers
+ if (params->host_mapped || params->type == RA_BUF_TYPE_TEX_UPLOAD)
+ return NULL;
+
+ struct ra_d3d11 *p = ra->priv;
+ HRESULT hr;
+
+ struct ra_buf *buf = talloc_zero(NULL, struct ra_buf);
+ buf->params = *params;
+ buf->params.initial_data = NULL;
+
+ struct d3d_buf *buf_p = buf->priv = talloc_zero(buf, struct d3d_buf);
+
+ D3D11_SUBRESOURCE_DATA *pdata = NULL;
+ if (params->initial_data)
+ pdata = &(D3D11_SUBRESOURCE_DATA) { .pSysMem = params->initial_data };
+
+ D3D11_BUFFER_DESC desc = { .ByteWidth = params->size };
+ switch (params->type) {
+ case RA_BUF_TYPE_SHADER_STORAGE:
+ desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
+ desc.ByteWidth = MP_ALIGN_UP(desc.ByteWidth, sizeof(float));
+ desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
+ break;
+ case RA_BUF_TYPE_UNIFORM:
+ desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+ desc.ByteWidth = MP_ALIGN_UP(desc.ByteWidth, sizeof(float[4]));
+ break;
+ }
+
+ hr = ID3D11Device_CreateBuffer(p->dev, &desc, pdata, &buf_p->buf);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create buffer: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ if (params->host_mutable) {
+ // D3D11 doesn't allow constant buffer updates that aren't aligned to a
+ // full constant boundary (vec4,) and some drivers don't allow partial
+ // constant buffer updates at all, but the RA consumer is allowed to
+ // partially update an ra_buf. The best way to handle partial updates
+ // without causing a pipeline stall is probably to keep a copy of the
+ // data in a staging buffer.
+
+ desc.Usage = D3D11_USAGE_STAGING;
+ desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+ desc.BindFlags = 0;
+ hr = ID3D11Device_CreateBuffer(p->dev, &desc, NULL, &buf_p->staging);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create staging buffer: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ }
+
+ if (params->type == RA_BUF_TYPE_SHADER_STORAGE) {
+ D3D11_UNORDERED_ACCESS_VIEW_DESC udesc = {
+ .Format = DXGI_FORMAT_R32_TYPELESS,
+ .ViewDimension = D3D11_UAV_DIMENSION_BUFFER,
+ .Buffer = {
+ .NumElements = desc.ByteWidth / sizeof(float),
+ .Flags = D3D11_BUFFER_UAV_FLAG_RAW,
+ },
+ };
+ hr = ID3D11Device_CreateUnorderedAccessView(p->dev,
+ (ID3D11Resource *)buf_p->buf, &udesc, &buf_p->uav);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create UAV: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ }
+
+ return buf;
+error:
+ buf_destroy(ra, buf);
+ return NULL;
+}
+
+static void buf_resolve(struct ra *ra, struct ra_buf *buf)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_buf *buf_p = buf->priv;
+
+ assert(buf->params.host_mutable);
+ if (!buf_p->data)
+ return;
+
+ ID3D11DeviceContext_Unmap(p->ctx, (ID3D11Resource *)buf_p->staging, 0);
+ buf_p->data = NULL;
+
+ // Synchronize the GPU buffer with the staging buffer
+ ID3D11DeviceContext_CopyResource(p->ctx, (ID3D11Resource *)buf_p->buf,
+ (ID3D11Resource *)buf_p->staging);
+}
+
+static void buf_update(struct ra *ra, struct ra_buf *buf, ptrdiff_t offset,
+ const void *data, size_t size)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_buf *buf_p = buf->priv;
+ HRESULT hr;
+
+ if (!buf_p->data) {
+ // If this is the first update after the buffer was created or after it
+ // has been used in a renderpass, it will be unmapped, so map it
+ D3D11_MAPPED_SUBRESOURCE map = {0};
+ hr = ID3D11DeviceContext_Map(p->ctx, (ID3D11Resource *)buf_p->staging,
+ 0, D3D11_MAP_WRITE, 0, &map);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to map resource\n");
+ return;
+ }
+ buf_p->data = map.pData;
+ }
+
+ char *cdata = buf_p->data;
+ memcpy(cdata + offset, data, size);
+}
+
+static const char *get_shader_target(struct ra *ra, enum glsl_shader type)
+{
+ struct ra_d3d11 *p = ra->priv;
+ switch (p->fl) {
+ default:
+ switch (type) {
+ case GLSL_SHADER_VERTEX: return "vs_5_0";
+ case GLSL_SHADER_FRAGMENT: return "ps_5_0";
+ case GLSL_SHADER_COMPUTE: return "cs_5_0";
+ }
+ break;
+ case D3D_FEATURE_LEVEL_10_1:
+ switch (type) {
+ case GLSL_SHADER_VERTEX: return "vs_4_1";
+ case GLSL_SHADER_FRAGMENT: return "ps_4_1";
+ case GLSL_SHADER_COMPUTE: return "cs_4_1";
+ }
+ break;
+ case D3D_FEATURE_LEVEL_10_0:
+ switch (type) {
+ case GLSL_SHADER_VERTEX: return "vs_4_0";
+ case GLSL_SHADER_FRAGMENT: return "ps_4_0";
+ case GLSL_SHADER_COMPUTE: return "cs_4_0";
+ }
+ break;
+ case D3D_FEATURE_LEVEL_9_3:
+ switch (type) {
+ case GLSL_SHADER_VERTEX: return "vs_4_0_level_9_3";
+ case GLSL_SHADER_FRAGMENT: return "ps_4_0_level_9_3";
+ }
+ break;
+ case D3D_FEATURE_LEVEL_9_2:
+ case D3D_FEATURE_LEVEL_9_1:
+ switch (type) {
+ case GLSL_SHADER_VERTEX: return "vs_4_0_level_9_1";
+ case GLSL_SHADER_FRAGMENT: return "ps_4_0_level_9_1";
+ }
+ break;
+ }
+ return NULL;
+}
+
+static const char *shader_type_name(enum glsl_shader type)
+{
+ switch (type) {
+ case GLSL_SHADER_VERTEX: return "vertex";
+ case GLSL_SHADER_FRAGMENT: return "fragment";
+ case GLSL_SHADER_COMPUTE: return "compute";
+ default: return "unknown";
+ }
+}
+
+static bool setup_clear_rpass(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ ID3DBlob *vs_blob = NULL;
+ ID3DBlob *ps_blob = NULL;
+ HRESULT hr;
+
+ hr = p->D3DCompile(clear_vs, sizeof(clear_vs), NULL, NULL, NULL, "main",
+ get_shader_target(ra, GLSL_SHADER_VERTEX),
+ D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &vs_blob, NULL);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to compile clear() vertex shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = ID3D11Device_CreateVertexShader(p->dev,
+ ID3D10Blob_GetBufferPointer(vs_blob), ID3D10Blob_GetBufferSize(vs_blob),
+ NULL, &p->clear_vs);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create clear() vertex shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = p->D3DCompile(clear_ps, sizeof(clear_ps), NULL, NULL, NULL, "main",
+ get_shader_target(ra, GLSL_SHADER_FRAGMENT),
+ D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &ps_blob, NULL);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to compile clear() pixel shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = ID3D11Device_CreatePixelShader(p->dev,
+ ID3D10Blob_GetBufferPointer(ps_blob), ID3D10Blob_GetBufferSize(ps_blob),
+ NULL, &p->clear_ps);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create clear() pixel shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ D3D11_INPUT_ELEMENT_DESC in_descs[] = {
+ { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0 },
+ };
+ hr = ID3D11Device_CreateInputLayout(p->dev, in_descs,
+ MP_ARRAY_SIZE(in_descs), ID3D10Blob_GetBufferPointer(vs_blob),
+ ID3D10Blob_GetBufferSize(vs_blob), &p->clear_layout);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create clear() IA layout: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ // clear() always draws to a quad covering the whole viewport
+ static const float verts[] = {
+ -1, -1,
+ 1, -1,
+ 1, 1,
+ -1, 1,
+ -1, -1,
+ 1, 1,
+ };
+ D3D11_BUFFER_DESC vdesc = {
+ .ByteWidth = sizeof(verts),
+ .Usage = D3D11_USAGE_IMMUTABLE,
+ .BindFlags = D3D11_BIND_VERTEX_BUFFER,
+ };
+ D3D11_SUBRESOURCE_DATA vdata = {
+ .pSysMem = verts,
+ };
+ hr = ID3D11Device_CreateBuffer(p->dev, &vdesc, &vdata, &p->clear_vbuf);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create clear() vertex buffer: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ D3D11_BUFFER_DESC cdesc = {
+ .ByteWidth = sizeof(float[4]),
+ .BindFlags = D3D11_BIND_CONSTANT_BUFFER,
+ };
+ hr = ID3D11Device_CreateBuffer(p->dev, &cdesc, NULL, &p->clear_cbuf);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create clear() constant buffer: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ SAFE_RELEASE(vs_blob);
+ SAFE_RELEASE(ps_blob);
+ return true;
+error:
+ SAFE_RELEASE(vs_blob);
+ SAFE_RELEASE(ps_blob);
+ return false;
+}
+
+static void clear_rpass(struct ra *ra, struct ra_tex *tex, float color[4],
+ struct mp_rect *rc)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_tex *tex_p = tex->priv;
+ struct ra_tex_params *params = &tex->params;
+
+ ID3D11DeviceContext_UpdateSubresource(p->ctx,
+ (ID3D11Resource *)p->clear_cbuf, 0, NULL, color, 0, 0);
+
+ ID3D11DeviceContext_IASetInputLayout(p->ctx, p->clear_layout);
+ ID3D11DeviceContext_IASetVertexBuffers(p->ctx, 0, 1, &p->clear_vbuf,
+ &(UINT) { sizeof(float[2]) }, &(UINT) { 0 });
+ ID3D11DeviceContext_IASetPrimitiveTopology(p->ctx,
+ D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ ID3D11DeviceContext_VSSetShader(p->ctx, p->clear_vs, NULL, 0);
+
+ ID3D11DeviceContext_RSSetViewports(p->ctx, 1, (&(D3D11_VIEWPORT) {
+ .Width = params->w,
+ .Height = params->h,
+ .MinDepth = 0,
+ .MaxDepth = 1,
+ }));
+ ID3D11DeviceContext_RSSetScissorRects(p->ctx, 1, (&(D3D11_RECT) {
+ .left = rc->x0,
+ .top = rc->y0,
+ .right = rc->x1,
+ .bottom = rc->y1,
+ }));
+ ID3D11DeviceContext_PSSetShader(p->ctx, p->clear_ps, NULL, 0);
+ ID3D11DeviceContext_PSSetConstantBuffers(p->ctx, 0, 1, &p->clear_cbuf);
+
+ ID3D11DeviceContext_OMSetRenderTargets(p->ctx, 1, &tex_p->rtv, NULL);
+ ID3D11DeviceContext_OMSetBlendState(p->ctx, NULL, NULL,
+ D3D11_DEFAULT_SAMPLE_MASK);
+
+ ID3D11DeviceContext_Draw(p->ctx, 6, 0);
+
+ ID3D11DeviceContext_PSSetConstantBuffers(p->ctx, 0, 1,
+ &(ID3D11Buffer *){ NULL });
+ ID3D11DeviceContext_OMSetRenderTargets(p->ctx, 0, NULL, NULL);
+}
+
+static void clear(struct ra *ra, struct ra_tex *tex, float color[4],
+ struct mp_rect *rc)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_tex *tex_p = tex->priv;
+ struct ra_tex_params *params = &tex->params;
+
+ if (!tex_p->rtv)
+ return;
+
+ if (rc->x0 || rc->y0 || rc->x1 != params->w || rc->y1 != params->h) {
+ if (p->has_clear_view) {
+ ID3D11DeviceContext1_ClearView(p->ctx1, (ID3D11View *)tex_p->rtv,
+ color, (&(D3D11_RECT) {
+ .left = rc->x0,
+ .top = rc->y0,
+ .right = rc->x1,
+ .bottom = rc->y1,
+ }), 1);
+ } else {
+ clear_rpass(ra, tex, color, rc);
+ }
+ } else {
+ ID3D11DeviceContext_ClearRenderTargetView(p->ctx, tex_p->rtv, color);
+ }
+}
+
+static bool setup_blit_rpass(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ ID3DBlob *vs_blob = NULL;
+ ID3DBlob *float_ps_blob = NULL;
+ HRESULT hr;
+
+ hr = p->D3DCompile(blit_vs, sizeof(blit_vs), NULL, NULL, NULL, "main",
+ get_shader_target(ra, GLSL_SHADER_VERTEX),
+ D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &vs_blob, NULL);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to compile blit() vertex shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = ID3D11Device_CreateVertexShader(p->dev,
+ ID3D10Blob_GetBufferPointer(vs_blob), ID3D10Blob_GetBufferSize(vs_blob),
+ NULL, &p->blit_vs);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create blit() vertex shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = p->D3DCompile(blit_float_ps, sizeof(blit_float_ps), NULL, NULL, NULL,
+ "main", get_shader_target(ra, GLSL_SHADER_FRAGMENT),
+ D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &float_ps_blob, NULL);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to compile blit() pixel shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = ID3D11Device_CreatePixelShader(p->dev,
+ ID3D10Blob_GetBufferPointer(float_ps_blob),
+ ID3D10Blob_GetBufferSize(float_ps_blob),
+ NULL, &p->blit_float_ps);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create blit() pixel shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ D3D11_INPUT_ELEMENT_DESC in_descs[] = {
+ { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8 },
+ };
+ hr = ID3D11Device_CreateInputLayout(p->dev, in_descs,
+ MP_ARRAY_SIZE(in_descs), ID3D10Blob_GetBufferPointer(vs_blob),
+ ID3D10Blob_GetBufferSize(vs_blob), &p->blit_layout);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create blit() IA layout: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ D3D11_BUFFER_DESC vdesc = {
+ .ByteWidth = sizeof(struct blit_vert[6]),
+ .Usage = D3D11_USAGE_DEFAULT,
+ .BindFlags = D3D11_BIND_VERTEX_BUFFER,
+ };
+ hr = ID3D11Device_CreateBuffer(p->dev, &vdesc, NULL, &p->blit_vbuf);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create blit() vertex buffer: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ // Blit always uses point sampling, regardless of the source texture
+ D3D11_SAMPLER_DESC sdesc = {
+ .AddressU = D3D11_TEXTURE_ADDRESS_CLAMP,
+ .AddressV = D3D11_TEXTURE_ADDRESS_CLAMP,
+ .AddressW = D3D11_TEXTURE_ADDRESS_CLAMP,
+ .ComparisonFunc = D3D11_COMPARISON_NEVER,
+ .MinLOD = 0,
+ .MaxLOD = D3D11_FLOAT32_MAX,
+ .MaxAnisotropy = 1,
+ };
+ hr = ID3D11Device_CreateSamplerState(p->dev, &sdesc, &p->blit_sampler);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create blit() sampler: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ SAFE_RELEASE(vs_blob);
+ SAFE_RELEASE(float_ps_blob);
+ return true;
+error:
+ SAFE_RELEASE(vs_blob);
+ SAFE_RELEASE(float_ps_blob);
+ return false;
+}
+
+static void blit_rpass(struct ra *ra, struct ra_tex *dst, struct ra_tex *src,
+ struct mp_rect *dst_rc, struct mp_rect *src_rc)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_tex *dst_p = dst->priv;
+ struct d3d_tex *src_p = src->priv;
+
+ float u_min = (double)src_rc->x0 / src->params.w;
+ float u_max = (double)src_rc->x1 / src->params.w;
+ float v_min = (double)src_rc->y0 / src->params.h;
+ float v_max = (double)src_rc->y1 / src->params.h;
+
+ struct blit_vert verts[6] = {
+ { .x = -1, .y = -1, .u = u_min, .v = v_max },
+ { .x = 1, .y = -1, .u = u_max, .v = v_max },
+ { .x = 1, .y = 1, .u = u_max, .v = v_min },
+ { .x = -1, .y = 1, .u = u_min, .v = v_min },
+ };
+ verts[4] = verts[0];
+ verts[5] = verts[2];
+ ID3D11DeviceContext_UpdateSubresource(p->ctx,
+ (ID3D11Resource *)p->blit_vbuf, 0, NULL, verts, 0, 0);
+
+ ID3D11DeviceContext_IASetInputLayout(p->ctx, p->blit_layout);
+ ID3D11DeviceContext_IASetVertexBuffers(p->ctx, 0, 1, &p->blit_vbuf,
+ &(UINT) { sizeof(verts[0]) }, &(UINT) { 0 });
+ ID3D11DeviceContext_IASetPrimitiveTopology(p->ctx,
+ D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ ID3D11DeviceContext_VSSetShader(p->ctx, p->blit_vs, NULL, 0);
+
+ ID3D11DeviceContext_RSSetViewports(p->ctx, 1, (&(D3D11_VIEWPORT) {
+ .TopLeftX = dst_rc->x0,
+ .TopLeftY = dst_rc->y0,
+ .Width = mp_rect_w(*dst_rc),
+ .Height = mp_rect_h(*dst_rc),
+ .MinDepth = 0,
+ .MaxDepth = 1,
+ }));
+ ID3D11DeviceContext_RSSetScissorRects(p->ctx, 1, (&(D3D11_RECT) {
+ .left = dst_rc->x0,
+ .top = dst_rc->y0,
+ .right = dst_rc->x1,
+ .bottom = dst_rc->y1,
+ }));
+
+ ID3D11DeviceContext_PSSetShader(p->ctx, p->blit_float_ps, NULL, 0);
+ ID3D11DeviceContext_PSSetShaderResources(p->ctx, 0, 1, &src_p->srv);
+ ID3D11DeviceContext_PSSetSamplers(p->ctx, 0, 1, &p->blit_sampler);
+
+ ID3D11DeviceContext_OMSetRenderTargets(p->ctx, 1, &dst_p->rtv, NULL);
+ ID3D11DeviceContext_OMSetBlendState(p->ctx, NULL, NULL,
+ D3D11_DEFAULT_SAMPLE_MASK);
+
+ ID3D11DeviceContext_Draw(p->ctx, 6, 0);
+
+ ID3D11DeviceContext_PSSetShaderResources(p->ctx, 0, 1,
+ &(ID3D11ShaderResourceView *) { NULL });
+ ID3D11DeviceContext_PSSetSamplers(p->ctx, 0, 1,
+ &(ID3D11SamplerState *) { NULL });
+ ID3D11DeviceContext_OMSetRenderTargets(p->ctx, 0, NULL, NULL);
+}
+
+static void blit(struct ra *ra, struct ra_tex *dst, struct ra_tex *src,
+ struct mp_rect *dst_rc_ptr, struct mp_rect *src_rc_ptr)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_tex *dst_p = dst->priv;
+ struct d3d_tex *src_p = src->priv;
+ struct mp_rect dst_rc = *dst_rc_ptr;
+ struct mp_rect src_rc = *src_rc_ptr;
+
+ assert(dst->params.dimensions == 2);
+ assert(src->params.dimensions == 2);
+
+ // A zero-sized target rectangle is a no-op
+ if (!mp_rect_w(dst_rc) || !mp_rect_h(dst_rc))
+ return;
+
+ // ra.h seems to imply that both dst_rc and src_rc can be flipped, but it's
+ // easier for blit_rpass() if only src_rc can be flipped, so unflip dst_rc.
+ if (dst_rc.x0 > dst_rc.x1) {
+ MPSWAP(int, dst_rc.x0, dst_rc.x1);
+ MPSWAP(int, src_rc.x0, src_rc.x1);
+ }
+ if (dst_rc.y0 > dst_rc.y1) {
+ MPSWAP(int, dst_rc.y0, dst_rc.y1);
+ MPSWAP(int, src_rc.y0, src_rc.y1);
+ }
+
+ // If format conversion, stretching or flipping is required, a renderpass
+ // must be used
+ if (dst->params.format != src->params.format ||
+ mp_rect_w(dst_rc) != mp_rect_w(src_rc) ||
+ mp_rect_h(dst_rc) != mp_rect_h(src_rc))
+ {
+ blit_rpass(ra, dst, src, &dst_rc, &src_rc);
+ } else {
+ int dst_sr = dst_p->array_slice >= 0 ? dst_p->array_slice : 0;
+ int src_sr = src_p->array_slice >= 0 ? src_p->array_slice : 0;
+ ID3D11DeviceContext_CopySubresourceRegion(p->ctx, dst_p->res, dst_sr,
+ dst_rc.x0, dst_rc.y0, 0, src_p->res, src_sr, (&(D3D11_BOX) {
+ .left = src_rc.x0,
+ .top = src_rc.y0,
+ .front = 0,
+ .right = src_rc.x1,
+ .bottom = src_rc.y1,
+ .back = 1,
+ }));
+ }
+}
+
+static int desc_namespace(enum ra_vartype type)
+{
+ // Images and SSBOs both use UAV bindings
+ if (type == RA_VARTYPE_IMG_W)
+ type = RA_VARTYPE_BUF_RW;
+ return type;
+}
+
+static bool compile_glsl(struct ra *ra, enum glsl_shader type,
+ const char *glsl, ID3DBlob **out)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct spirv_compiler *spirv = p->spirv;
+ void *ta_ctx = talloc_new(NULL);
+ crossc_compiler *cross = NULL;
+ const char *hlsl = NULL;
+ ID3DBlob *errors = NULL;
+ bool success = false;
+ HRESULT hr;
+
+ int cross_shader_model;
+ if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
+ cross_shader_model = 50;
+ } else if (p->fl >= D3D_FEATURE_LEVEL_10_1) {
+ cross_shader_model = 41;
+ } else {
+ cross_shader_model = 40;
+ }
+
+ int64_t start_us = mp_time_us();
+
+ bstr spv_module;
+ if (!spirv->fns->compile_glsl(spirv, ta_ctx, type, glsl, &spv_module))
+ goto done;
+
+ int64_t shaderc_us = mp_time_us();
+
+ cross = crossc_hlsl_create((uint32_t*)spv_module.start,
+ spv_module.len / sizeof(uint32_t));
+
+ crossc_hlsl_set_shader_model(cross, cross_shader_model);
+ crossc_set_flip_vert_y(cross, type == GLSL_SHADER_VERTEX);
+
+ hlsl = crossc_compile(cross);
+ if (!hlsl) {
+ MP_ERR(ra, "SPIRV-Cross failed: %s\n", crossc_strerror(cross));
+ goto done;
+ }
+
+ int64_t cross_us = mp_time_us();
+
+ hr = p->D3DCompile(hlsl, strlen(hlsl), NULL, NULL, NULL, "main",
+ get_shader_target(ra, type), D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, out,
+ &errors);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "D3DCompile failed: %s\n%.*s", mp_HRESULT_to_str(hr),
+ (int)ID3D10Blob_GetBufferSize(errors),
+ (char*)ID3D10Blob_GetBufferPointer(errors));
+ goto done;
+ }
+
+ int64_t d3dcompile_us = mp_time_us();
+
+ MP_VERBOSE(ra, "Compiled a %s shader in %lldus\n", shader_type_name(type),
+ d3dcompile_us - start_us);
+ MP_VERBOSE(ra, "shaderc: %lldus, SPIRV-Cross: %lldus, D3DCompile: %lldus\n",
+ shaderc_us - start_us,
+ cross_us - shaderc_us,
+ d3dcompile_us - cross_us);
+
+ success = true;
+done:;
+ int level = success ? MSGL_DEBUG : MSGL_ERR;
+ MP_MSG(ra, level, "GLSL source:\n");
+ mp_log_source(ra->log, level, glsl);
+ if (hlsl) {
+ MP_MSG(ra, level, "HLSL source:\n");
+ mp_log_source(ra->log, level, hlsl);
+ }
+ SAFE_RELEASE(errors);
+ crossc_destroy(cross);
+ talloc_free(ta_ctx);
+ return success;
+}
+
+static void renderpass_destroy(struct ra *ra, struct ra_renderpass *pass)
+{
+ if (!pass)
+ return;
+ struct d3d_rpass *pass_p = pass->priv;
+
+ SAFE_RELEASE(pass_p->vs);
+ SAFE_RELEASE(pass_p->ps);
+ SAFE_RELEASE(pass_p->cs);
+ SAFE_RELEASE(pass_p->layout);
+ SAFE_RELEASE(pass_p->bstate);
+ talloc_free(pass);
+}
+
+static D3D11_BLEND map_ra_blend(enum ra_blend blend)
+{
+ switch (blend) {
+ default:
+ case RA_BLEND_ZERO: return D3D11_BLEND_ZERO;
+ case RA_BLEND_ONE: return D3D11_BLEND_ONE;
+ case RA_BLEND_SRC_ALPHA: return D3D11_BLEND_SRC_ALPHA;
+ case RA_BLEND_ONE_MINUS_SRC_ALPHA: return D3D11_BLEND_INV_SRC_ALPHA;
+ };
+}
+
+static size_t vbuf_upload(struct ra *ra, void *data, size_t size)
+{
+ struct ra_d3d11 *p = ra->priv;
+ HRESULT hr;
+
+ // Arbitrary size limit in case there is an insane number of vertices
+ if (size > 1e9) {
+ MP_ERR(ra, "Vertex buffer is too large\n");
+ return -1;
+ }
+
+ // If the vertex data doesn't fit, realloc the vertex buffer
+ if (size > p->vbuf_size) {
+ size_t new_size = p->vbuf_size;
+ // Arbitrary base size
+ if (!new_size)
+ new_size = 64 * 1024;
+ while (new_size < size)
+ new_size *= 2;
+
+ ID3D11Buffer *new_buf;
+ D3D11_BUFFER_DESC vbuf_desc = {
+ .ByteWidth = new_size,
+ .Usage = D3D11_USAGE_DYNAMIC,
+ .BindFlags = D3D11_BIND_VERTEX_BUFFER,
+ .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
+ };
+ hr = ID3D11Device_CreateBuffer(p->dev, &vbuf_desc, NULL, &new_buf);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create vertex buffer: %s\n",
+ mp_HRESULT_to_str(hr));
+ return -1;
+ }
+
+ SAFE_RELEASE(p->vbuf);
+ p->vbuf = new_buf;
+ p->vbuf_size = new_size;
+ p->vbuf_used = 0;
+ }
+
+ bool discard = false;
+ size_t offset = p->vbuf_used;
+ if (offset + size > p->vbuf_size) {
+ // We reached the end of the buffer, so discard and wrap around
+ discard = true;
+ offset = 0;
+ }
+
+ D3D11_MAPPED_SUBRESOURCE map = { 0 };
+ hr = ID3D11DeviceContext_Map(p->ctx, (ID3D11Resource *)p->vbuf, 0,
+ discard ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE,
+ 0, &map);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to map vertex buffer: %s\n", mp_HRESULT_to_str(hr));
+ return -1;
+ }
+
+ char *cdata = map.pData;
+ memcpy(cdata + offset, data, size);
+
+ ID3D11DeviceContext_Unmap(p->ctx, (ID3D11Resource *)p->vbuf, 0);
+
+ p->vbuf_used = offset + size;
+ return offset;
+}
+
+static const char cache_magic[4] = "RD11";
+static const int cache_version = 2;
+
+struct cache_header {
+ char magic[sizeof(cache_magic)];
+ int cache_version;
+ char compiler[SPIRV_NAME_MAX_LEN];
+ int spv_compiler_version;
+ uint32_t cross_version;
+ struct dll_version d3d_compiler_version;
+ int feature_level;
+ size_t vert_bytecode_len;
+ size_t frag_bytecode_len;
+ size_t comp_bytecode_len;
+};
+
+static void load_cached_program(struct ra *ra,
+ const struct ra_renderpass_params *params,
+ bstr *vert_bc,
+ bstr *frag_bc,
+ bstr *comp_bc)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct spirv_compiler *spirv = p->spirv;
+ bstr cache = params->cached_program;
+
+ if (cache.len < sizeof(struct cache_header))
+ return;
+
+ struct cache_header *header = (struct cache_header *)cache.start;
+ cache = bstr_cut(cache, sizeof(*header));
+
+ if (strncmp(header->magic, cache_magic, sizeof(cache_magic)) != 0)
+ return;
+ if (header->cache_version != cache_version)
+ return;
+ if (strncmp(header->compiler, spirv->name, sizeof(header->compiler)) != 0)
+ return;
+ if (header->spv_compiler_version != spirv->compiler_version)
+ return;
+ if (header->cross_version != crossc_version())
+ return;
+ if (!dll_version_equal(header->d3d_compiler_version, p->d3d_compiler_ver))
+ return;
+ if (header->feature_level != p->fl)
+ return;
+
+ if (header->vert_bytecode_len && vert_bc) {
+ *vert_bc = bstr_splice(cache, 0, header->vert_bytecode_len);
+ MP_VERBOSE(ra, "Using cached vertex shader\n");
+ }
+ cache = bstr_cut(cache, header->vert_bytecode_len);
+
+ if (header->frag_bytecode_len && frag_bc) {
+ *frag_bc = bstr_splice(cache, 0, header->frag_bytecode_len);
+ MP_VERBOSE(ra, "Using cached fragment shader\n");
+ }
+ cache = bstr_cut(cache, header->frag_bytecode_len);
+
+ if (header->comp_bytecode_len && comp_bc) {
+ *comp_bc = bstr_splice(cache, 0, header->comp_bytecode_len);
+ MP_VERBOSE(ra, "Using cached compute shader\n");
+ }
+ cache = bstr_cut(cache, header->comp_bytecode_len);
+}
+
+static void save_cached_program(struct ra *ra, struct ra_renderpass *pass,
+ bstr vert_bc,
+ bstr frag_bc,
+ bstr comp_bc)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct spirv_compiler *spirv = p->spirv;
+
+ struct cache_header header = {
+ .cache_version = cache_version,
+ .spv_compiler_version = p->spirv->compiler_version,
+ .cross_version = crossc_version(),
+ .d3d_compiler_version = p->d3d_compiler_ver,
+ .feature_level = p->fl,
+ .vert_bytecode_len = vert_bc.len,
+ .frag_bytecode_len = frag_bc.len,
+ .comp_bytecode_len = comp_bc.len,
+ };
+ strncpy(header.magic, cache_magic, sizeof(header.magic));
+ strncpy(header.compiler, spirv->name, sizeof(header.compiler));
+
+ struct bstr *prog = &pass->params.cached_program;
+ bstr_xappend(pass, prog, (bstr){ (char *) &header, sizeof(header) });
+ bstr_xappend(pass, prog, vert_bc);
+ bstr_xappend(pass, prog, frag_bc);
+ bstr_xappend(pass, prog, comp_bc);
+}
+
+static struct ra_renderpass *renderpass_create_raster(struct ra *ra,
+ struct ra_renderpass *pass, const struct ra_renderpass_params *params)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_rpass *pass_p = pass->priv;
+ ID3DBlob *vs_blob = NULL;
+ ID3DBlob *ps_blob = NULL;
+ HRESULT hr;
+
+ // load_cached_program will load compiled shader bytecode into vert_bc and
+ // frag_bc if the cache is valid. If not, vert_bc/frag_bc will remain NULL.
+ bstr vert_bc = {0};
+ bstr frag_bc = {0};
+ load_cached_program(ra, params, &vert_bc, &frag_bc, NULL);
+
+ if (!vert_bc.start) {
+ if (!compile_glsl(ra, GLSL_SHADER_VERTEX, params->vertex_shader,
+ &vs_blob))
+ goto error;
+ vert_bc = (bstr){
+ ID3D10Blob_GetBufferPointer(vs_blob),
+ ID3D10Blob_GetBufferSize(vs_blob),
+ };
+ }
+
+ hr = ID3D11Device_CreateVertexShader(p->dev, vert_bc.start, vert_bc.len,
+ NULL, &pass_p->vs);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create vertex shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ if (!frag_bc.start) {
+ if (!compile_glsl(ra, GLSL_SHADER_FRAGMENT, params->frag_shader,
+ &ps_blob))
+ goto error;
+ frag_bc = (bstr){
+ ID3D10Blob_GetBufferPointer(ps_blob),
+ ID3D10Blob_GetBufferSize(ps_blob),
+ };
+ }
+
+ hr = ID3D11Device_CreatePixelShader(p->dev, frag_bc.start, frag_bc.len,
+ NULL, &pass_p->ps);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create pixel shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ D3D11_INPUT_ELEMENT_DESC *in_descs = talloc_array(pass,
+ D3D11_INPUT_ELEMENT_DESC, params->num_vertex_attribs);
+ for (int i = 0; i < params->num_vertex_attribs; i++) {
+ struct ra_renderpass_input *inp = &params->vertex_attribs[i];
+
+ DXGI_FORMAT fmt = DXGI_FORMAT_UNKNOWN;
+ switch (inp->type) {
+ case RA_VARTYPE_FLOAT:
+ switch (inp->dim_v) {
+ case 1: fmt = DXGI_FORMAT_R32_FLOAT; break;
+ case 2: fmt = DXGI_FORMAT_R32G32_FLOAT; break;
+ case 3: fmt = DXGI_FORMAT_R32G32B32_FLOAT; break;
+ case 4: fmt = DXGI_FORMAT_R32G32B32A32_FLOAT; break;
+ }
+ break;
+ case RA_VARTYPE_BYTE_UNORM:
+ switch (inp->dim_v) {
+ case 1: fmt = DXGI_FORMAT_R8_UNORM; break;
+ case 2: fmt = DXGI_FORMAT_R8G8_UNORM; break;
+ // There is no 3-component 8-bit DXGI format
+ case 4: fmt = DXGI_FORMAT_R8G8B8A8_UNORM; break;
+ }
+ break;
+ }
+ if (fmt == DXGI_FORMAT_UNKNOWN) {
+ MP_ERR(ra, "Could not find suitable vertex input format\n");
+ goto error;
+ }
+
+ in_descs[i] = (D3D11_INPUT_ELEMENT_DESC) {
+ // The semantic name doesn't mean much and is just used to verify
+ // the input description matches the shader. SPIRV-Cross always
+ // uses TEXCOORD, so we should too.
+ .SemanticName = "TEXCOORD",
+ .SemanticIndex = i,
+ .AlignedByteOffset = inp->offset,
+ .Format = fmt,
+ };
+ }
+
+ hr = ID3D11Device_CreateInputLayout(p->dev, in_descs,
+ params->num_vertex_attribs, vert_bc.start, vert_bc.len,
+ &pass_p->layout);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create IA layout: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ talloc_free(in_descs);
+ in_descs = NULL;
+
+ D3D11_BLEND_DESC bdesc = {
+ .RenderTarget[0] = {
+ .BlendEnable = params->enable_blend,
+ .SrcBlend = map_ra_blend(params->blend_src_rgb),
+ .DestBlend = map_ra_blend(params->blend_dst_rgb),
+ .BlendOp = D3D11_BLEND_OP_ADD,
+ .SrcBlendAlpha = map_ra_blend(params->blend_src_alpha),
+ .DestBlendAlpha = map_ra_blend(params->blend_dst_alpha),
+ .BlendOpAlpha = D3D11_BLEND_OP_ADD,
+ .RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL,
+ },
+ };
+ hr = ID3D11Device_CreateBlendState(p->dev, &bdesc, &pass_p->bstate);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create blend state: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ save_cached_program(ra, pass, vert_bc, frag_bc, (bstr){0});
+
+ SAFE_RELEASE(vs_blob);
+ SAFE_RELEASE(ps_blob);
+ return pass;
+
+error:
+ renderpass_destroy(ra, pass);
+ SAFE_RELEASE(vs_blob);
+ SAFE_RELEASE(ps_blob);
+ return NULL;
+}
+
+static struct ra_renderpass *renderpass_create_compute(struct ra *ra,
+ struct ra_renderpass *pass, const struct ra_renderpass_params *params)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_rpass *pass_p = pass->priv;
+ ID3DBlob *cs_blob = NULL;
+ HRESULT hr;
+
+ bstr comp_bc = {0};
+ load_cached_program(ra, params, NULL, NULL, &comp_bc);
+
+ if (!comp_bc.start) {
+ if (!compile_glsl(ra, GLSL_SHADER_COMPUTE, params->compute_shader,
+ &cs_blob))
+ goto error;
+ comp_bc = (bstr){
+ ID3D10Blob_GetBufferPointer(cs_blob),
+ ID3D10Blob_GetBufferSize(cs_blob),
+ };
+ }
+ hr = ID3D11Device_CreateComputeShader(p->dev, comp_bc.start, comp_bc.len,
+ NULL, &pass_p->cs);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create compute shader: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ save_cached_program(ra, pass, (bstr){0}, (bstr){0}, comp_bc);
+
+ SAFE_RELEASE(cs_blob);
+ return pass;
+error:
+ renderpass_destroy(ra, pass);
+ SAFE_RELEASE(cs_blob);
+ return NULL;
+}
+
+static struct ra_renderpass *renderpass_create(struct ra *ra,
+ const struct ra_renderpass_params *params)
+{
+ struct ra_renderpass *pass = talloc_zero(NULL, struct ra_renderpass);
+ pass->params = *ra_renderpass_params_copy(pass, params);
+ pass->params.cached_program = (bstr){0};
+ pass->priv = talloc_zero(pass, struct d3d_rpass);
+
+ if (params->type == RA_RENDERPASS_TYPE_COMPUTE) {
+ return renderpass_create_compute(ra, pass, params);
+ } else {
+ return renderpass_create_raster(ra, pass, params);
+ }
+}
+
+static void renderpass_run_raster(struct ra *ra,
+ const struct ra_renderpass_run_params *params,
+ ID3D11Buffer *ubos[], int ubos_len,
+ ID3D11SamplerState *samplers[],
+ ID3D11ShaderResourceView *srvs[],
+ int samplers_len,
+ ID3D11UnorderedAccessView *uavs[],
+ int uavs_len)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct ra_renderpass *pass = params->pass;
+ struct d3d_rpass *pass_p = pass->priv;
+
+ UINT vbuf_offset = vbuf_upload(ra, params->vertex_data,
+ pass->params.vertex_stride * params->vertex_count);
+ if (vbuf_offset == (UINT)-1)
+ return;
+
+ ID3D11DeviceContext_IASetInputLayout(p->ctx, pass_p->layout);
+ ID3D11DeviceContext_IASetVertexBuffers(p->ctx, 0, 1, &p->vbuf,
+ &pass->params.vertex_stride, &vbuf_offset);
+ ID3D11DeviceContext_IASetPrimitiveTopology(p->ctx,
+ D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ ID3D11DeviceContext_VSSetShader(p->ctx, pass_p->vs, NULL, 0);
+
+ ID3D11DeviceContext_RSSetViewports(p->ctx, 1, (&(D3D11_VIEWPORT) {
+ .TopLeftX = params->viewport.x0,
+ .TopLeftY = params->viewport.y0,
+ .Width = mp_rect_w(params->viewport),
+ .Height = mp_rect_h(params->viewport),
+ .MinDepth = 0,
+ .MaxDepth = 1,
+ }));
+ ID3D11DeviceContext_RSSetScissorRects(p->ctx, 1, (&(D3D11_RECT) {
+ .left = params->scissors.x0,
+ .top = params->scissors.y0,
+ .right = params->scissors.x1,
+ .bottom = params->scissors.y1,
+ }));
+ ID3D11DeviceContext_PSSetShader(p->ctx, pass_p->ps, NULL, 0);
+ ID3D11DeviceContext_PSSetConstantBuffers(p->ctx, 0, ubos_len, ubos);
+ ID3D11DeviceContext_PSSetShaderResources(p->ctx, 0, samplers_len, srvs);
+ ID3D11DeviceContext_PSSetSamplers(p->ctx, 0, samplers_len, samplers);
+
+ struct ra_tex *target = params->target;
+ struct d3d_tex *target_p = target->priv;
+ ID3D11DeviceContext_OMSetRenderTargetsAndUnorderedAccessViews(p->ctx, 1,
+ &target_p->rtv, NULL, 1, uavs_len, uavs, NULL);
+ ID3D11DeviceContext_OMSetBlendState(p->ctx, pass_p->bstate, NULL,
+ D3D11_DEFAULT_SAMPLE_MASK);
+
+ ID3D11DeviceContext_Draw(p->ctx, params->vertex_count, 0);
+
+ // Unbind everything. It's easier to do this than to actually track state,
+ // and if we leave the RTV bound, it could trip up D3D's conflict checker.
+ for (int i = 0; i < ubos_len; i++)
+ ubos[i] = NULL;
+ for (int i = 0; i < samplers_len; i++) {
+ samplers[i] = NULL;
+ srvs[i] = NULL;
+ }
+ for (int i = 0; i < uavs_len; i++)
+ uavs[i] = NULL;
+ ID3D11DeviceContext_PSSetConstantBuffers(p->ctx, 0, ubos_len, ubos);
+ ID3D11DeviceContext_PSSetShaderResources(p->ctx, 0, samplers_len, srvs);
+ ID3D11DeviceContext_PSSetSamplers(p->ctx, 0, samplers_len, samplers);
+ ID3D11DeviceContext_OMSetRenderTargetsAndUnorderedAccessViews(p->ctx, 0,
+ NULL, NULL, 1, uavs_len, uavs, NULL);
+}
+
+static void renderpass_run_compute(struct ra *ra,
+ const struct ra_renderpass_run_params *params,
+ ID3D11Buffer *ubos[], int ubos_len,
+ ID3D11SamplerState *samplers[],
+ ID3D11ShaderResourceView *srvs[],
+ int samplers_len,
+ ID3D11UnorderedAccessView *uavs[],
+ int uavs_len)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct ra_renderpass *pass = params->pass;
+ struct d3d_rpass *pass_p = pass->priv;
+
+ ID3D11DeviceContext_CSSetShader(p->ctx, pass_p->cs, NULL, 0);
+ ID3D11DeviceContext_CSSetConstantBuffers(p->ctx, 0, ubos_len, ubos);
+ ID3D11DeviceContext_CSSetShaderResources(p->ctx, 0, samplers_len, srvs);
+ ID3D11DeviceContext_CSSetSamplers(p->ctx, 0, samplers_len, samplers);
+ ID3D11DeviceContext_CSSetUnorderedAccessViews(p->ctx, 0, uavs_len, uavs,
+ NULL);
+
+ ID3D11DeviceContext_Dispatch(p->ctx, params->compute_groups[0],
+ params->compute_groups[1],
+ params->compute_groups[2]);
+
+ for (int i = 0; i < ubos_len; i++)
+ ubos[i] = NULL;
+ for (int i = 0; i < samplers_len; i++) {
+ samplers[i] = NULL;
+ srvs[i] = NULL;
+ }
+ for (int i = 0; i < uavs_len; i++)
+ uavs[i] = NULL;
+ ID3D11DeviceContext_CSSetConstantBuffers(p->ctx, 0, ubos_len, ubos);
+ ID3D11DeviceContext_CSSetShaderResources(p->ctx, 0, samplers_len, srvs);
+ ID3D11DeviceContext_CSSetSamplers(p->ctx, 0, samplers_len, samplers);
+ ID3D11DeviceContext_CSSetUnorderedAccessViews(p->ctx, 0, uavs_len, uavs,
+ NULL);
+}
+
+static void renderpass_run(struct ra *ra,
+ const struct ra_renderpass_run_params *params)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct ra_renderpass *pass = params->pass;
+ enum ra_renderpass_type type = pass->params.type;
+
+ ID3D11Buffer *ubos[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] = {0};
+ int ubos_len = 0;
+
+ ID3D11SamplerState *samplers[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT] = {0};
+ ID3D11ShaderResourceView *srvs[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT] = {0};
+ int samplers_len = 0;
+
+ ID3D11UnorderedAccessView *uavs[D3D11_1_UAV_SLOT_COUNT] = {0};
+ int uavs_len = 0;
+
+ // In a raster pass, one of the UAV slots is used by the runtime for the RTV
+ int uavs_max = type == RA_RENDERPASS_TYPE_COMPUTE ? p->max_uavs
+ : p->max_uavs - 1;
+
+ // Gather the input variables used in this pass. These will be mapped to
+ // HLSL registers.
+ for (int i = 0; i < params->num_values; i++) {
+ struct ra_renderpass_input_val *val = &params->values[i];
+ int binding = pass->params.inputs[val->index].binding;
+ switch (pass->params.inputs[val->index].type) {
+ case RA_VARTYPE_BUF_RO:
+ if (binding > MP_ARRAY_SIZE(ubos)) {
+ MP_ERR(ra, "Too many constant buffers in pass\n");
+ return;
+ }
+ struct ra_buf *buf_ro = *(struct ra_buf **)val->data;
+ buf_resolve(ra, buf_ro);
+ struct d3d_buf *buf_ro_p = buf_ro->priv;
+ ubos[binding] = buf_ro_p->buf;
+ ubos_len = MPMAX(ubos_len, binding + 1);
+ break;
+ case RA_VARTYPE_BUF_RW:
+ if (binding > uavs_max) {
+ MP_ERR(ra, "Too many UAVs in pass\n");
+ return;
+ }
+ struct ra_buf *buf_rw = *(struct ra_buf **)val->data;
+ buf_resolve(ra, buf_rw);
+ struct d3d_buf *buf_rw_p = buf_rw->priv;
+ uavs[binding] = buf_rw_p->uav;
+ uavs_len = MPMAX(uavs_len, binding + 1);
+ break;
+ case RA_VARTYPE_TEX:
+ if (binding > MP_ARRAY_SIZE(samplers)) {
+ MP_ERR(ra, "Too many textures in pass\n");
+ return;
+ }
+ struct ra_tex *tex = *(struct ra_tex **)val->data;
+ struct d3d_tex *tex_p = tex->priv;
+ samplers[binding] = tex_p->sampler;
+ srvs[binding] = tex_p->srv;
+ samplers_len = MPMAX(samplers_len, binding + 1);
+ break;
+ case RA_VARTYPE_IMG_W:
+ if (binding > uavs_max) {
+ MP_ERR(ra, "Too many UAVs in pass\n");
+ return;
+ }
+ struct ra_tex *img = *(struct ra_tex **)val->data;
+ struct d3d_tex *img_p = img->priv;
+ uavs[binding] = img_p->uav;
+ uavs_len = MPMAX(uavs_len, binding + 1);
+ break;
+ }
+ }
+
+ if (type == RA_RENDERPASS_TYPE_COMPUTE) {
+ renderpass_run_compute(ra, params, ubos, ubos_len, samplers, srvs,
+ samplers_len, uavs, uavs_len);
+ } else {
+ renderpass_run_raster(ra, params, ubos, ubos_len, samplers, srvs,
+ samplers_len, uavs, uavs_len);
+ }
+}
+
+static void timer_destroy(struct ra *ra, ra_timer *ratimer)
+{
+ if (!ratimer)
+ return;
+ struct d3d_timer *timer = ratimer;
+
+ SAFE_RELEASE(timer->ts_start);
+ SAFE_RELEASE(timer->ts_end);
+ SAFE_RELEASE(timer->disjoint);
+ talloc_free(timer);
+}
+
+static ra_timer *timer_create(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ if (!p->has_timestamp_queries)
+ return NULL;
+
+ struct d3d_timer *timer = talloc_zero(NULL, struct d3d_timer);
+ HRESULT hr;
+
+ hr = ID3D11Device_CreateQuery(p->dev,
+ &(D3D11_QUERY_DESC) { D3D11_QUERY_TIMESTAMP }, &timer->ts_start);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create start query: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ hr = ID3D11Device_CreateQuery(p->dev,
+ &(D3D11_QUERY_DESC) { D3D11_QUERY_TIMESTAMP }, &timer->ts_end);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create end query: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ // Measuring duration in D3D11 requires three queries: start and end
+ // timestamps, and a disjoint query containing a flag which says whether
+ // the timestamps are usable or if a discontinuity occured between them,
+ // like a change in power state or clock speed. The disjoint query also
+ // contains the timer frequency, so the timestamps are useless without it.
+ hr = ID3D11Device_CreateQuery(p->dev,
+ &(D3D11_QUERY_DESC) { D3D11_QUERY_TIMESTAMP_DISJOINT }, &timer->disjoint);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create timer query: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+
+ return timer;
+error:
+ timer_destroy(ra, timer);
+ return NULL;
+}
+
+static uint64_t timestamp_to_ns(uint64_t timestamp, uint64_t freq)
+{
+ static const uint64_t ns_per_s = 1000000000llu;
+ return timestamp / freq * ns_per_s + timestamp % freq * ns_per_s / freq;
+}
+
+static uint64_t timer_get_result(struct ra *ra, ra_timer *ratimer)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_timer *timer = ratimer;
+ HRESULT hr;
+
+ UINT64 start, end;
+ D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
+
+ hr = ID3D11DeviceContext_GetData(p->ctx,
+ (ID3D11Asynchronous *)timer->ts_end, &end, sizeof(end),
+ D3D11_ASYNC_GETDATA_DONOTFLUSH);
+ if (FAILED(hr) || hr == S_FALSE)
+ return 0;
+ hr = ID3D11DeviceContext_GetData(p->ctx,
+ (ID3D11Asynchronous *)timer->ts_start, &start, sizeof(start),
+ D3D11_ASYNC_GETDATA_DONOTFLUSH);
+ if (FAILED(hr) || hr == S_FALSE)
+ return 0;
+ hr = ID3D11DeviceContext_GetData(p->ctx,
+ (ID3D11Asynchronous *)timer->disjoint, &dj, sizeof(dj),
+ D3D11_ASYNC_GETDATA_DONOTFLUSH);
+ if (FAILED(hr) || hr == S_FALSE || dj.Disjoint || !dj.Frequency)
+ return 0;
+
+ return timestamp_to_ns(end - start, dj.Frequency);
+}
+
+static void timer_start(struct ra *ra, ra_timer *ratimer)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_timer *timer = ratimer;
+
+ // Latch the last result of this ra_timer (returned by timer_stop)
+ timer->result = timer_get_result(ra, ratimer);
+
+ ID3D11DeviceContext_Begin(p->ctx, (ID3D11Asynchronous *)timer->disjoint);
+ ID3D11DeviceContext_End(p->ctx, (ID3D11Asynchronous *)timer->ts_start);
+}
+
+static uint64_t timer_stop(struct ra *ra, ra_timer *ratimer)
+{
+ struct ra_d3d11 *p = ra->priv;
+ struct d3d_timer *timer = ratimer;
+
+ ID3D11DeviceContext_End(p->ctx, (ID3D11Asynchronous *)timer->ts_end);
+ ID3D11DeviceContext_End(p->ctx, (ID3D11Asynchronous *)timer->disjoint);
+
+ return timer->result;
+}
+
+static int map_msg_severity(D3D11_MESSAGE_SEVERITY sev)
+{
+ switch (sev) {
+ case D3D11_MESSAGE_SEVERITY_CORRUPTION:
+ return MSGL_FATAL;
+ case D3D11_MESSAGE_SEVERITY_ERROR:
+ return MSGL_ERR;
+ case D3D11_MESSAGE_SEVERITY_WARNING:
+ return MSGL_WARN;
+ default:
+ case D3D11_MESSAGE_SEVERITY_INFO:
+ case D3D11_MESSAGE_SEVERITY_MESSAGE:
+ return MSGL_DEBUG;
+ }
+}
+
+static void debug_marker(struct ra *ra, const char *msg)
+{
+ struct ra_d3d11 *p = ra->priv;
+ void *talloc_ctx = talloc_new(NULL);
+ HRESULT hr;
+
+ if (!p->iqueue)
+ goto done;
+
+ // Copy debug-layer messages to mpv's log output
+ bool printed_header = false;
+ uint64_t messages = ID3D11InfoQueue_GetNumStoredMessages(p->iqueue);
+ for (uint64_t i = 0; i < messages; i++) {
+ size_t len;
+ hr = ID3D11InfoQueue_GetMessage(p->iqueue, i, NULL, &len);
+ if (FAILED(hr) || !len)
+ goto done;
+
+ D3D11_MESSAGE *d3dmsg = talloc_size(talloc_ctx, len);
+ hr = ID3D11InfoQueue_GetMessage(p->iqueue, i, d3dmsg, &len);
+ if (FAILED(hr))
+ goto done;
+
+ int msgl = map_msg_severity(d3dmsg->Severity);
+ if (mp_msg_test(ra->log, msgl)) {
+ if (!printed_header)
+ MP_INFO(ra, "%s:\n", msg);
+ printed_header = true;
+
+ MP_MSG(ra, msgl, "%d: %.*s\n", (int)d3dmsg->ID,
+ (int)d3dmsg->DescriptionByteLength, d3dmsg->pDescription);
+ talloc_free(d3dmsg);
+ }
+ }
+
+ ID3D11InfoQueue_ClearStoredMessages(p->iqueue);
+done:
+ talloc_free(talloc_ctx);
+}
+
+static void destroy(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+
+ // Release everything except the interfaces needed to perform leak checking
+ SAFE_RELEASE(p->clear_ps);
+ SAFE_RELEASE(p->clear_vs);
+ SAFE_RELEASE(p->clear_layout);
+ SAFE_RELEASE(p->clear_vbuf);
+ SAFE_RELEASE(p->clear_cbuf);
+ SAFE_RELEASE(p->blit_float_ps);
+ SAFE_RELEASE(p->blit_vs);
+ SAFE_RELEASE(p->blit_layout);
+ SAFE_RELEASE(p->blit_vbuf);
+ SAFE_RELEASE(p->blit_sampler);
+ SAFE_RELEASE(p->vbuf);
+ SAFE_RELEASE(p->ctx1);
+ SAFE_RELEASE(p->dev1);
+ SAFE_RELEASE(p->dev);
+
+ if (p->debug && p->ctx) {
+ // Destroy the device context synchronously so referenced objects don't
+ // show up in the leak check
+ ID3D11DeviceContext_ClearState(p->ctx);
+ ID3D11DeviceContext_Flush(p->ctx);
+ }
+ SAFE_RELEASE(p->ctx);
+
+ if (p->debug) {
+ // Report any leaked objects
+ debug_marker(ra, "after destroy");
+ ID3D11Debug_ReportLiveDeviceObjects(p->debug, D3D11_RLDO_DETAIL);
+ debug_marker(ra, "after leak check");
+ ID3D11Debug_ReportLiveDeviceObjects(p->debug, D3D11_RLDO_SUMMARY);
+ debug_marker(ra, "after leak summary");
+ }
+ SAFE_RELEASE(p->debug);
+ SAFE_RELEASE(p->iqueue);
+
+ talloc_free(ra);
+}
+
+static struct ra_fns ra_fns_d3d11 = {
+ .destroy = destroy,
+ .tex_create = tex_create,
+ .tex_destroy = tex_destroy,
+ .tex_upload = tex_upload,
+ .buf_create = buf_create,
+ .buf_destroy = buf_destroy,
+ .buf_update = buf_update,
+ .clear = clear,
+ .blit = blit,
+ .uniform_layout = std140_layout,
+ .desc_namespace = desc_namespace,
+ .renderpass_create = renderpass_create,
+ .renderpass_destroy = renderpass_destroy,
+ .renderpass_run = renderpass_run,
+ .timer_create = timer_create,
+ .timer_destroy = timer_destroy,
+ .timer_start = timer_start,
+ .timer_stop = timer_stop,
+ .debug_marker = debug_marker,
+};
+
+void ra_d3d11_flush(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ ID3D11DeviceContext_Flush(p->ctx);
+}
+
+static void init_debug_layer(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ HRESULT hr;
+
+ hr = ID3D11Device_QueryInterface(p->dev, &IID_ID3D11Debug,
+ (void**)&p->debug);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to get debug device: %s\n", mp_HRESULT_to_str(hr));
+ return;
+ }
+
+ hr = ID3D11Device_QueryInterface(p->dev, &IID_ID3D11InfoQueue,
+ (void**)&p->iqueue);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to get info queue: %s\n", mp_HRESULT_to_str(hr));
+ return;
+ }
+
+ // Store an unlimited amount of messages in the buffer. This is fine
+ // because we flush stored messages regularly (in debug_marker.)
+ ID3D11InfoQueue_SetMessageCountLimit(p->iqueue, -1);
+
+ // Filter some annoying messages
+ D3D11_MESSAGE_ID deny_ids[] = {
+ // This error occurs during context creation when we try to figure out
+ // the real maximum texture size by attempting to create a texture
+ // larger than the current feature level allows.
+ D3D11_MESSAGE_ID_CREATETEXTURE2D_INVALIDDIMENSIONS,
+
+ // These are normal. The RA timer queue habitually reuses timer objects
+ // without retrieving the results.
+ D3D11_MESSAGE_ID_QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS,
+ D3D11_MESSAGE_ID_QUERY_END_ABANDONING_PREVIOUS_RESULTS,
+ };
+ D3D11_INFO_QUEUE_FILTER filter = {
+ .DenyList = {
+ .NumIDs = MP_ARRAY_SIZE(deny_ids),
+ .pIDList = deny_ids,
+ },
+ };
+ ID3D11InfoQueue_PushStorageFilter(p->iqueue, &filter);
+}
+
+static struct dll_version get_dll_version(HMODULE dll)
+{
+ void *ctx = talloc_new(NULL);
+ struct dll_version ret = { 0 };
+
+ HRSRC rsrc = FindResourceW(dll, MAKEINTRESOURCEW(VS_VERSION_INFO),
+ MAKEINTRESOURCEW(VS_FILE_INFO));
+ if (!rsrc)
+ goto done;
+ DWORD size = SizeofResource(dll, rsrc);
+ HGLOBAL res = LoadResource(dll, rsrc);
+ if (!res)
+ goto done;
+ void *ptr = LockResource(res);
+ if (!ptr)
+ goto done;
+ void *copy = talloc_memdup(ctx, ptr, size);
+
+ VS_FIXEDFILEINFO *ffi;
+ UINT ffi_len;
+ if (!VerQueryValueW(copy, L"\\", (void**)&ffi, &ffi_len))
+ goto done;
+ if (ffi_len < sizeof(*ffi))
+ goto done;
+
+ ret.major = HIWORD(ffi->dwFileVersionMS);
+ ret.minor = LOWORD(ffi->dwFileVersionMS);
+ ret.build = HIWORD(ffi->dwFileVersionLS);
+ ret.revision = LOWORD(ffi->dwFileVersionLS);
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+static bool load_d3d_compiler(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ HMODULE d3dcompiler = NULL;
+
+ // Try the inbox D3DCompiler first (Windows 8.1 and up)
+ if (IsWindows8Point1OrGreater()) {
+ d3dcompiler = LoadLibraryExW(L"d3dcompiler_47.dll", NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
+ }
+ // Check for a packaged version of d3dcompiler_47.dll
+ if (!d3dcompiler)
+ d3dcompiler = LoadLibraryW(L"d3dcompiler_47.dll");
+ // Try d3dcompiler_46.dll from the Windows 8 SDK
+ if (!d3dcompiler)
+ d3dcompiler = LoadLibraryW(L"d3dcompiler_46.dll");
+ // Try d3dcompiler_43.dll from the June 2010 DirectX SDK
+ if (!d3dcompiler)
+ d3dcompiler = LoadLibraryW(L"d3dcompiler_43.dll");
+ // Can't find any compiler DLL, so give up
+ if (!d3dcompiler)
+ return false;
+
+ p->d3d_compiler_ver = get_dll_version(d3dcompiler);
+
+ p->D3DCompile = (pD3DCompile)GetProcAddress(d3dcompiler, "D3DCompile");
+ if (!p->D3DCompile)
+ return false;
+ return true;
+}
+
+static void find_max_texture_dimension(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+
+ D3D11_TEXTURE2D_DESC desc = {
+ .Width = ra->max_texture_wh,
+ .Height = ra->max_texture_wh,
+ .MipLevels = 1,
+ .ArraySize = 1,
+ .SampleDesc.Count = 1,
+ .Format = DXGI_FORMAT_R8_UNORM,
+ .BindFlags = D3D11_BIND_SHADER_RESOURCE,
+ };
+ while (true) {
+ desc.Height = desc.Width *= 2;
+ if (desc.Width >= 0x8000000u)
+ return;
+ if (FAILED(ID3D11Device_CreateTexture2D(p->dev, &desc, NULL, NULL)))
+ return;
+ ra->max_texture_wh = desc.Width;
+ }
+}
+
+struct ra *ra_d3d11_create(ID3D11Device *dev, struct mp_log *log,
+ struct spirv_compiler *spirv)
+{
+ HRESULT hr;
+
+ struct ra *ra = talloc_zero(NULL, struct ra);
+ ra->log = log;
+ ra->fns = &ra_fns_d3d11;
+
+ // Even Direct3D 10level9 supports 3D textures
+ ra->caps = RA_CAP_TEX_3D | RA_CAP_DIRECT_UPLOAD | RA_CAP_BUF_RO |
+ RA_CAP_BLIT | spirv->ra_caps;
+
+ ra->glsl_version = spirv->glsl_version;
+ ra->glsl_vulkan = true;
+
+ struct ra_d3d11 *p = ra->priv = talloc_zero(ra, struct ra_d3d11);
+ p->spirv = spirv;
+
+ int minor = 0;
+ ID3D11Device_AddRef(dev);
+ p->dev = dev;
+ ID3D11Device_GetImmediateContext(p->dev, &p->ctx);
+ hr = ID3D11Device_QueryInterface(p->dev, &IID_ID3D11Device1,
+ (void**)&p->dev1);
+ if (SUCCEEDED(hr)) {
+ minor = 1;
+ ID3D11Device1_GetImmediateContext1(p->dev1, &p->ctx1);
+
+ D3D11_FEATURE_DATA_D3D11_OPTIONS fopts = { 0 };
+ hr = ID3D11Device_CheckFeatureSupport(p->dev,
+ D3D11_FEATURE_D3D11_OPTIONS, &fopts, sizeof(fopts));
+ if (SUCCEEDED(hr)) {
+ p->has_clear_view = fopts.ClearView;
+ }
+ }
+
+ MP_VERBOSE(ra, "Using Direct3D 11.%d runtime\n", minor);
+
+ p->fl = ID3D11Device_GetFeatureLevel(p->dev);
+ if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
+ ra->max_texture_wh = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+ } else if (p->fl >= D3D_FEATURE_LEVEL_10_0) {
+ ra->max_texture_wh = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+ } else if (p->fl >= D3D_FEATURE_LEVEL_9_3) {
+ ra->max_texture_wh = D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+ } else {
+ ra->max_texture_wh = D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+ }
+
+ if (p->fl >= D3D_FEATURE_LEVEL_11_0)
+ ra->caps |= RA_CAP_GATHER;
+ if (p->fl >= D3D_FEATURE_LEVEL_10_0)
+ ra->caps |= RA_CAP_FRAGCOORD;
+
+ // Some 10_0 hardware has compute shaders, but only 11_0 has image load/store
+ if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
+ ra->caps |= RA_CAP_COMPUTE | RA_CAP_BUF_RW;
+ ra->max_shmem = 32 * 1024;
+ }
+
+ if (p->fl >= D3D_FEATURE_LEVEL_11_1) {
+ p->max_uavs = D3D11_1_UAV_SLOT_COUNT;
+ } else {
+ p->max_uavs = D3D11_PS_CS_UAV_REGISTER_COUNT;
+ }
+
+ if (ID3D11Device_GetCreationFlags(p->dev) & D3D11_CREATE_DEVICE_DEBUG)
+ init_debug_layer(ra);
+
+ // Some level 9_x devices don't have timestamp queries
+ hr = ID3D11Device_CreateQuery(p->dev,
+ &(D3D11_QUERY_DESC) { D3D11_QUERY_TIMESTAMP }, NULL);
+ p->has_timestamp_queries = SUCCEEDED(hr);
+
+ // According to MSDN, the above texture sizes are just minimums and drivers
+ // may support larger textures. See:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ff476874.aspx
+ find_max_texture_dimension(ra);
+ MP_VERBOSE(ra, "Maximum Texture2D size: %dx%d\n", ra->max_texture_wh,
+ ra->max_texture_wh);
+
+ if (!load_d3d_compiler(ra)) {
+ MP_FATAL(ra, "Could not find D3DCompiler DLL\n");
+ goto error;
+ }
+
+ MP_VERBOSE(ra, "D3DCompiler version: %u.%u.%u.%u\n",
+ p->d3d_compiler_ver.major, p->d3d_compiler_ver.minor,
+ p->d3d_compiler_ver.build, p->d3d_compiler_ver.revision);
+
+ setup_formats(ra);
+
+ // The rasterizer state never changes, so set it up here
+ ID3D11RasterizerState *rstate;
+ D3D11_RASTERIZER_DESC rdesc = {
+ .FillMode = D3D11_FILL_SOLID,
+ .CullMode = D3D11_CULL_NONE,
+ .FrontCounterClockwise = FALSE,
+ .DepthClipEnable = TRUE, // Required for 10level9
+ .ScissorEnable = TRUE,
+ };
+ hr = ID3D11Device_CreateRasterizerState(p->dev, &rdesc, &rstate);
+ if (FAILED(hr)) {
+ MP_ERR(ra, "Failed to create rasterizer state: %s\n", mp_HRESULT_to_str(hr));
+ goto error;
+ }
+ ID3D11DeviceContext_RSSetState(p->ctx, rstate);
+ SAFE_RELEASE(rstate);
+
+ // If the device doesn't support ClearView, we have to set up a
+ // shader-based clear() implementation
+ if (!p->has_clear_view && !setup_clear_rpass(ra))
+ goto error;
+
+ if (!setup_blit_rpass(ra))
+ goto error;
+
+ return ra;
+
+error:
+ destroy(ra);
+ return NULL;
+}
+
+ID3D11Device *ra_d3d11_get_device(struct ra *ra)
+{
+ struct ra_d3d11 *p = ra->priv;
+ ID3D11Device_AddRef(p->dev);
+ return p->dev;
+}
+
+bool ra_is_d3d11(struct ra *ra)
+{
+ return ra->fns == &ra_fns_d3d11;
+}
diff --git a/video/out/d3d11/ra_d3d11.h b/video/out/d3d11/ra_d3d11.h
new file mode 100644
index 0000000..54033b6
--- /dev/null
+++ b/video/out/d3d11/ra_d3d11.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <stdbool.h>
+#include <windows.h>
+#include <d3d11.h>
+#include <dxgi1_2.h>
+
+#include "video/out/gpu/ra.h"
+#include "video/out/gpu/spirv.h"
+
+// Create an RA instance from a D3D11 device. This takes a reference to the
+// device, which is released when the RA instance is destroyed.
+struct ra *ra_d3d11_create(ID3D11Device *device, struct mp_log *log,
+ struct spirv_compiler *spirv);
+
+// Flush the immediate context of the wrapped D3D11 device
+void ra_d3d11_flush(struct ra *ra);
+
+// Create an RA texture from a D3D11 resource. This takes a reference to the
+// texture, which is released when the RA texture is destroyed.
+struct ra_tex *ra_d3d11_wrap_tex(struct ra *ra, ID3D11Resource *res);
+
+// As above, but for a D3D11VA video resource. The fmt parameter selects which
+// plane of a planar format will be mapped when the RA texture is used.
+// array_slice should be set for texture arrays and is ignored for non-arrays.
+struct ra_tex *ra_d3d11_wrap_tex_video(struct ra *ra, ID3D11Texture2D *res,
+ int w, int h, int array_slice,
+ const struct ra_format *fmt);
+
+// Get the underlying D3D11 device from an RA instance. The returned device is
+// refcounted and must be released by the caller.
+ID3D11Device *ra_d3d11_get_device(struct ra *ra);
+
+// True if the RA instance was created with ra_d3d11_create()
+bool ra_is_d3d11(struct ra *ra);
diff --git a/video/out/drm_atomic.c b/video/out/drm_atomic.c
new file mode 100644
index 0000000..7a55483
--- /dev/null
+++ b/video/out/drm_atomic.c
@@ -0,0 +1,245 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "drm_atomic.h"
+
+int drm_object_create_properties(struct mp_log *log, int fd,
+ struct drm_object *object)
+{
+ object->props = drmModeObjectGetProperties(fd, object->id, object->type);
+ if (object->props) {
+ object->props_info = talloc_zero_size(NULL, object->props->count_props
+ * sizeof(object->props_info));
+ if (object->props_info) {
+ for (int i = 0; i < object->props->count_props; i++)
+ object->props_info[i] = drmModeGetProperty(fd, object->props->props[i]);
+ } else {
+ mp_err(log, "Out of memory\n");
+ goto fail;
+ }
+ } else {
+ mp_err(log, "Failed to retrieve properties for object id %d\n", object->id);
+ goto fail;
+ }
+
+ return 0;
+
+ fail:
+ drm_object_free_properties(object);
+ return -1;
+}
+
+void drm_object_free_properties(struct drm_object *object)
+{
+ if (object->props) {
+ for (int i = 0; i < object->props->count_props; i++) {
+ if (object->props_info[i]) {
+ drmModeFreeProperty(object->props_info[i]);
+ object->props_info[i] = NULL;
+ }
+ }
+
+ talloc_free(object->props_info);
+ object->props_info = NULL;
+
+ drmModeFreeObjectProperties(object->props);
+ object->props = NULL;
+ }
+}
+
+int drm_object_get_property(struct drm_object *object, char *name, uint64_t *value)
+{
+ for (int i = 0; i < object->props->count_props; i++) {
+ if (strcasecmp(name, object->props_info[i]->name) == 0) {
+ *value = object->props->prop_values[i];
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+int drm_object_set_property(drmModeAtomicReq *request, struct drm_object *object,
+ char *name, uint64_t value)
+{
+ for (int i = 0; i < object->props->count_props; i++) {
+ if (strcasecmp(name, object->props_info[i]->name) == 0) {
+ return drmModeAtomicAddProperty(request, object->id,
+ object->props_info[i]->prop_id, value);
+ }
+ }
+
+ return -EINVAL;
+}
+
+struct drm_object * drm_object_create(struct mp_log *log, int fd,
+ uint32_t object_id, uint32_t type)
+{
+ struct drm_object *obj = NULL;
+ obj = talloc_zero(NULL, struct drm_object);
+ obj->id = object_id;
+ obj->type = type;
+
+ if (drm_object_create_properties(log, fd, obj)) {
+ talloc_free(obj);
+ return NULL;
+ }
+
+ return obj;
+}
+
+void drm_object_free(struct drm_object *object)
+{
+ if (object) {
+ drm_object_free_properties(object);
+ talloc_free(object);
+ }
+}
+
+void drm_object_print_info(struct mp_log *log, struct drm_object *object)
+{
+ mp_err(log, "Object ID = %d (type = %x) has %d properties\n",
+ object->id, object->type, object->props->count_props);
+
+ for (int i = 0; i < object->props->count_props; i++)
+ mp_err(log, " Property '%s' = %lld\n", object->props_info[i]->name,
+ (long long)object->props->prop_values[i]);
+}
+
+struct drm_atomic_context *drm_atomic_create_context(struct mp_log *log, int fd,
+ int crtc_id, int overlay_id)
+{
+ drmModePlane *drmplane = NULL;
+ drmModePlaneRes *plane_res = NULL;
+ drmModeRes *res = NULL;
+ struct drm_object *plane = NULL;
+ struct drm_atomic_context *ctx;
+ int crtc_index = -1;
+ int layercount = 0;
+ uint64_t value;
+
+ res = drmModeGetResources(fd);
+ if (!res) {
+ mp_err(log, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno));
+ goto fail;
+ }
+
+ plane_res = drmModeGetPlaneResources(fd);
+ if (!plane_res) {
+ mp_err(log, "Cannot retrieve plane ressources: %s\n", mp_strerror(errno));
+ goto fail;
+ }
+
+ ctx = talloc_zero(NULL, struct drm_atomic_context);
+ if (!ctx) {
+ mp_err(log, "Out of memory\n");
+ goto fail;
+ }
+
+ ctx->fd = fd;
+ ctx->crtc = drm_object_create(log, ctx->fd, crtc_id, DRM_MODE_OBJECT_CRTC);
+ if (!ctx->crtc) {
+ mp_err(log, "Failed to create CRTC object\n");
+ goto fail;
+ }
+
+ for (int i = 0; i < res->count_crtcs; i++) {
+ if (res->crtcs[i] == crtc_id) {
+ crtc_index = i;
+ break;
+ }
+ }
+
+ for (unsigned int j = 0; j < plane_res->count_planes; j++) {
+
+ drmplane = drmModeGetPlane (ctx->fd, plane_res->planes[j]);
+ if (drmplane->possible_crtcs & (1 << crtc_index)) {
+ plane = drm_object_create(log, ctx->fd, drmplane->plane_id,
+ DRM_MODE_OBJECT_PLANE);
+
+ if (plane) {
+ if (drm_object_get_property(plane, "TYPE", &value) == -EINVAL) {
+ mp_err(log, "Unable to retrieve type property from plane %d\n", j);
+ goto fail;
+ } else {
+ if ((value == DRM_PLANE_TYPE_OVERLAY) &&
+ (layercount == overlay_id)) {
+ ctx->overlay_plane = plane;
+ }
+ else if (value == DRM_PLANE_TYPE_PRIMARY) {
+ ctx->primary_plane = plane;
+ }
+ else {
+ drm_object_free(plane);
+ plane = NULL;
+ }
+
+ if (value == DRM_PLANE_TYPE_OVERLAY)
+ layercount++;
+ }
+ } else {
+ mp_err(log, "Failed to create Plane object from plane ID %d\n",
+ drmplane->plane_id);
+ goto fail;
+ }
+ }
+ drmModeFreePlane(drmplane);
+ drmplane = NULL;
+ }
+
+ if (!ctx->primary_plane) {
+ mp_err(log, "Failed to find primary plane\n");
+ goto fail;
+ }
+
+ if (!ctx->overlay_plane) {
+ mp_err(log, "Failed to find overlay plane with id=%d\n", overlay_id);
+ goto fail;
+ }
+
+ mp_verbose(log, "Found Primary plane with ID %d, overlay with ID %d\n",
+ ctx->primary_plane->id, ctx->overlay_plane->id);
+
+ drmModeFreePlaneResources(plane_res);
+ drmModeFreeResources(res);
+ return ctx;
+
+
+fail:
+ if (res)
+ drmModeFreeResources(res);
+ if (plane_res)
+ drmModeFreePlaneResources(plane_res);
+ if (drmplane)
+ drmModeFreePlane(drmplane);
+ if (plane)
+ drm_object_free(plane);
+ return NULL;
+}
+
+void drm_atomic_destroy_context(struct drm_atomic_context *ctx)
+{
+ drm_object_free(ctx->crtc);
+ drm_object_free(ctx->primary_plane);
+ drm_object_free(ctx->overlay_plane);
+ talloc_free(ctx);
+}
diff --git a/video/out/drm_atomic.h b/video/out/drm_atomic.h
new file mode 100644
index 0000000..d0ebdb9
--- /dev/null
+++ b/video/out/drm_atomic.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_DRMATOMIC_H
+#define MP_DRMATOMIC_H
+
+#include <stdlib.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "common/msg.h"
+
+struct drm_object {
+ uint32_t id;
+ uint32_t type;
+ drmModeObjectProperties *props;
+ drmModePropertyRes **props_info;
+};
+
+struct drm_atomic_context {
+ int fd;
+
+ struct drm_object *crtc;
+ struct drm_object *primary_plane;
+ struct drm_object *overlay_plane;
+
+ drmModeAtomicReq *request;
+};
+
+
+int drm_object_create_properties(struct mp_log *log, int fd, struct drm_object *object);
+void drm_object_free_properties(struct drm_object *object);
+int drm_object_get_property(struct drm_object *object, char *name, uint64_t *value);
+int drm_object_set_property(drmModeAtomicReq *request, struct drm_object *object, char *name, uint64_t value);
+struct drm_object * drm_object_create(struct mp_log *log, int fd, uint32_t object_id, uint32_t type);
+void drm_object_free(struct drm_object *object);
+void drm_object_print_info(struct mp_log *log, struct drm_object *object);
+struct drm_atomic_context *drm_atomic_create_context(struct mp_log *log, int fd, int crtc_id, int overlay_id);
+void drm_atomic_destroy_context(struct drm_atomic_context *ctx);
+
+#endif // MP_DRMATOMIC_H
diff --git a/video/out/drm_common.c b/video/out/drm_common.c
index aea4afa..8402ac7 100644
--- a/video/out/drm_common.c
+++ b/video/out/drm_common.c
@@ -41,6 +41,18 @@
static int vt_switcher_pipe[2];
+#define OPT_BASE_STRUCT struct drm_opts
+const struct m_sub_options drm_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_STRING_VALIDATE("drm-connector", drm_connector_spec,
+ 0, drm_validate_connector_opt),
+ OPT_INT("drm-mode", drm_mode_id, 0),
+ OPT_INT("drm-overlay", drm_overlay_id, 0),
+ {0},
+ },
+ .size = sizeof(struct drm_opts),
+};
+
static const char *connector_names[] = {
"Unknown", // DRM_MODE_CONNECTOR_Unknown
"VGA", // DRM_MODE_CONNECTOR_VGA
@@ -222,7 +234,7 @@ static void parse_connector_spec(struct mp_log *log,
struct kms *kms_create(struct mp_log *log, const char *connector_spec,
- int mode_id)
+ int mode_id, int overlay_id)
{
int card_no = -1;
char *connector_name = NULL;
@@ -260,6 +272,23 @@ struct kms *kms_create(struct mp_log *log, const char *connector_spec,
if (!setup_mode(kms, mode_id))
goto err;
+ // Universal planes allows accessing all the planes (including primary)
+ if (drmSetClientCap(kms->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
+ mp_err(log, "Failed to set Universal planes capability\n");
+ }
+
+ if (drmSetClientCap(kms->fd, DRM_CLIENT_CAP_ATOMIC, 1)) {
+ mp_verbose(log, "No DRM Atomic support found\n");
+ } else {
+ mp_verbose(log, "DRM Atomic support found\n");
+ kms->atomic_context = drm_atomic_create_context(kms->log, kms->fd, kms->crtc_id, overlay_id);
+ if (!kms->atomic_context) {
+ mp_err(log, "Failed to create DRM atomic context\n");
+ goto err;
+ }
+ }
+
+
drmModeFreeResources(res);
return kms;
@@ -284,6 +313,10 @@ void kms_destroy(struct kms *kms)
drmModeFreeEncoder(kms->encoder);
kms->encoder = NULL;
}
+ if (kms->atomic_context) {
+ drm_atomic_destroy_context(kms->atomic_context);
+ }
+
close(kms->fd);
talloc_free(kms);
}
diff --git a/video/out/drm_common.h b/video/out/drm_common.h
index 6796472..ff913ff 100644
--- a/video/out/drm_common.h
+++ b/video/out/drm_common.h
@@ -22,6 +22,7 @@
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "options/m_option.h"
+#include "drm_atomic.h"
struct kms {
struct mp_log *log;
@@ -31,6 +32,7 @@ struct kms {
drmModeModeInfo mode;
uint32_t crtc_id;
int card_no;
+ struct drm_atomic_context *atomic_context;
};
struct vt_switcher {
@@ -40,6 +42,12 @@ struct vt_switcher {
void *handler_data[2];
};
+struct drm_opts {
+ char *drm_connector_spec;
+ int drm_mode_id;
+ int drm_overlay_id;
+};
+
bool vt_switcher_init(struct vt_switcher *s, struct mp_log *log);
void vt_switcher_destroy(struct vt_switcher *s);
void vt_switcher_poll(struct vt_switcher *s, int timeout_ms);
@@ -51,7 +59,7 @@ void vt_switcher_release(struct vt_switcher *s, void (*handler)(void*),
void *user_data);
struct kms *kms_create(struct mp_log *log, const char *connector_spec,
- int mode_id);
+ int mode_id, int overlay_id);
void kms_destroy(struct kms *kms);
double kms_get_display_fps(const struct kms *kms);
diff --git a/video/out/drm_prime.c b/video/out/drm_prime.c
new file mode 100644
index 0000000..253fbb6
--- /dev/null
+++ b/video/out/drm_prime.c
@@ -0,0 +1,85 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "common/msg.h"
+#include "drm_common.h"
+#include "drm_prime.h"
+
+int drm_prime_create_framebuffer(struct mp_log *log, int fd, AVDRMFrameDescriptor *descriptor, int width, int height,
+ struct drm_prime_framebuffer *framebuffer)
+{
+ AVDRMLayerDescriptor *layer = NULL;
+ uint32_t pitches[4], offsets[4], handles[4];
+ int ret, layer_fd;
+
+ if (descriptor && descriptor->nb_layers) {
+ *framebuffer = (struct drm_prime_framebuffer){0};
+
+ for (int object = 0; object < descriptor->nb_objects; object++) {
+ ret = drmPrimeFDToHandle(fd, descriptor->objects[object].fd, &framebuffer->gem_handles[object]);
+ if (ret < 0) {
+ mp_err(log, "Failed to retrieve the Prime Handle from handle %d (%d).\n", object, descriptor->objects[object].fd);
+ goto fail;
+ }
+ }
+
+ layer = &descriptor->layers[0];
+
+ for (int plane = 0; plane < AV_DRM_MAX_PLANES; plane++) {
+ layer_fd = framebuffer->gem_handles[layer->planes[plane].object_index];
+ if (layer_fd && layer->planes[plane].pitch) {
+ pitches[plane] = layer->planes[plane].pitch;
+ offsets[plane] = layer->planes[plane].offset;
+ handles[plane] = layer_fd;
+ } else {
+ pitches[plane] = 0;
+ offsets[plane] = 0;
+ handles[plane] = 0;
+ }
+ }
+
+ ret = drmModeAddFB2(fd, width, height, layer->format,
+ handles, pitches, offsets, &framebuffer->fb_id, 0);
+ if (ret < 0) {
+ mp_err(log, "Failed to create framebuffer on layer %d.\n", 0);
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ memset(framebuffer, 0, sizeof(*framebuffer));
+ return -1;
+}
+
+void drm_prime_destroy_framebuffer(struct mp_log *log, int fd, struct drm_prime_framebuffer *framebuffer)
+{
+ if (framebuffer->fb_id)
+ drmModeRmFB(fd, framebuffer->fb_id);
+
+ for (int i = 0; i < AV_DRM_MAX_PLANES; i++) {
+ if (framebuffer->gem_handles[i])
+ drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &framebuffer->gem_handles[i]);
+ }
+
+ memset(framebuffer, 0, sizeof(*framebuffer));
+}
diff --git a/video/out/wayland/memfile.h b/video/out/drm_prime.h
index 67cdb1b..0653fdb 100644
--- a/video/out/wayland/memfile.h
+++ b/video/out/drm_prime.h
@@ -1,6 +1,5 @@
/*
- * This file is part of mpv video player.
- * Copyright © 2014 Alexander Preisinger <alexander.preisinger@gmail.com>
+ * This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -16,11 +15,19 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef MPLAYER_WAYLAND_MEMFILE_H
-#define MPLAYER_WAYLAND_MEMFILE_H
+#ifndef DRM_PRIME_H
+#define DRM_PRIME_H
-// create file decsriptor to memory space without filesystem representation
-// truncates to size immediately
-int memfile_create(off_t size);
+#include <libavutil/hwcontext_drm.h>
-#endif /* MPLAYER_WAYLAND_MEMFILE_H */
+#include "common/msg.h"
+
+struct drm_prime_framebuffer {
+ uint32_t fb_id;
+ uint32_t gem_handles[AV_DRM_MAX_PLANES];
+};
+
+int drm_prime_create_framebuffer(struct mp_log *log, int fd, AVDRMFrameDescriptor *descriptor, int width, int height,
+ struct drm_prime_framebuffer *framebuffers);
+void drm_prime_destroy_framebuffer(struct mp_log *log, int fd, struct drm_prime_framebuffer *framebuffers);
+#endif // DRM_PRIME_H
diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c
new file mode 100644
index 0000000..36f9c2d
--- /dev/null
+++ b/video/out/gpu/context.c
@@ -0,0 +1,223 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+
+#include "config.h"
+#include "common/common.h"
+#include "common/msg.h"
+#include "options/options.h"
+#include "options/m_option.h"
+#include "video/out/vo.h"
+
+#include "context.h"
+#include "spirv.h"
+
+/* OpenGL */
+extern const struct ra_ctx_fns ra_ctx_glx;
+extern const struct ra_ctx_fns ra_ctx_glx_probe;
+extern const struct ra_ctx_fns ra_ctx_x11_egl;
+extern const struct ra_ctx_fns ra_ctx_drm_egl;
+extern const struct ra_ctx_fns ra_ctx_cocoa;
+extern const struct ra_ctx_fns ra_ctx_wayland_egl;
+extern const struct ra_ctx_fns ra_ctx_wgl;
+extern const struct ra_ctx_fns ra_ctx_angle;
+extern const struct ra_ctx_fns ra_ctx_dxgl;
+extern const struct ra_ctx_fns ra_ctx_rpi;
+extern const struct ra_ctx_fns ra_ctx_android;
+extern const struct ra_ctx_fns ra_ctx_mali_fbdev;
+extern const struct ra_ctx_fns ra_ctx_vdpauglx;
+
+/* Vulkan */
+extern const struct ra_ctx_fns ra_ctx_vulkan_wayland;
+extern const struct ra_ctx_fns ra_ctx_vulkan_win;
+extern const struct ra_ctx_fns ra_ctx_vulkan_xlib;
+
+/* Direct3D 11 */
+extern const struct ra_ctx_fns ra_ctx_d3d11;
+
+static const struct ra_ctx_fns *contexts[] = {
+#if HAVE_D3D11
+ &ra_ctx_d3d11,
+#endif
+
+// OpenGL contexts:
+#if HAVE_ANDROID
+ &ra_ctx_android,
+#endif
+#if HAVE_RPI
+ &ra_ctx_rpi,
+#endif
+#if HAVE_GL_COCOA
+ &ra_ctx_cocoa,
+#endif
+#if HAVE_EGL_ANGLE_WIN32
+ &ra_ctx_angle,
+#endif
+#if HAVE_GL_WIN32
+ &ra_ctx_wgl,
+#endif
+#if HAVE_GL_DXINTEROP
+ &ra_ctx_dxgl,
+#endif
+#if HAVE_GL_X11
+ &ra_ctx_glx_probe,
+#endif
+#if HAVE_EGL_X11
+ &ra_ctx_x11_egl,
+#endif
+#if HAVE_GL_X11
+ &ra_ctx_glx,
+#endif
+#if HAVE_GL_WAYLAND
+ &ra_ctx_wayland_egl,
+#endif
+#if HAVE_EGL_DRM
+ &ra_ctx_drm_egl,
+#endif
+#if HAVE_MALI_FBDEV
+ &ra_ctx_mali_fbdev,
+#endif
+#if HAVE_VDPAU_GL_X11
+ &ra_ctx_vdpauglx,
+#endif
+
+// Vulkan contexts:
+#if HAVE_VULKAN
+
+#if HAVE_WIN32_DESKTOP
+ &ra_ctx_vulkan_win,
+#endif
+#if HAVE_WAYLAND
+ &ra_ctx_vulkan_wayland,
+#endif
+#if HAVE_X11
+ &ra_ctx_vulkan_xlib,
+#endif
+
+#endif
+};
+
+int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, struct bstr param)
+{
+ if (bstr_equals0(param, "help")) {
+ mp_info(log, "GPU APIs (contexts):\n");
+ mp_info(log, " auto (autodetect)\n");
+ for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++)
+ mp_info(log, " %s (%s)\n", contexts[n]->type, contexts[n]->name);
+ return M_OPT_EXIT;
+ }
+ if (bstr_equals0(param, "auto"))
+ return 1;
+ for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
+ if (bstr_equals0(param, contexts[i]->type))
+ return 1;
+ }
+ return M_OPT_INVALID;
+}
+
+int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, struct bstr param)
+{
+ if (bstr_equals0(param, "help")) {
+ mp_info(log, "GPU contexts (APIs):\n");
+ mp_info(log, " auto (autodetect)\n");
+ for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++)
+ mp_info(log, " %s (%s)\n", contexts[n]->name, contexts[n]->type);
+ return M_OPT_EXIT;
+ }
+ if (bstr_equals0(param, "auto"))
+ return 1;
+ for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
+ if (bstr_equals0(param, contexts[i]->name))
+ return 1;
+ }
+ return M_OPT_INVALID;
+}
+
+// Create a VO window and create a RA context on it.
+// vo_flags: passed to the backend's create window function
+struct ra_ctx *ra_ctx_create(struct vo *vo, const char *context_type,
+ const char *context_name, struct ra_ctx_opts opts)
+{
+ bool api_auto = !context_type || strcmp(context_type, "auto") == 0;
+ bool ctx_auto = !context_name || strcmp(context_name, "auto") == 0;
+
+ if (ctx_auto) {
+ MP_VERBOSE(vo, "Probing for best GPU context.\n");
+ opts.probing = true;
+ }
+
+ // Hack to silence backend (X11/Wayland/etc.) errors. Kill it once backends
+ // are separate from `struct vo`
+ bool old_probing = vo->probing;
+ vo->probing = opts.probing;
+
+ for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
+ if (!opts.probing && strcmp(contexts[i]->name, context_name) != 0)
+ continue;
+ if (!api_auto && strcmp(contexts[i]->type, context_type) != 0)
+ continue;
+
+ struct ra_ctx *ctx = talloc_ptrtype(NULL, ctx);
+ *ctx = (struct ra_ctx) {
+ .vo = vo,
+ .global = vo->global,
+ .log = mp_log_new(ctx, vo->log, contexts[i]->type),
+ .opts = opts,
+ .fns = contexts[i],
+ };
+
+ MP_VERBOSE(ctx, "Initializing GPU context '%s'\n", ctx->fns->name);
+ if (contexts[i]->init(ctx)) {
+ vo->probing = old_probing;
+ return ctx;
+ }
+
+ talloc_free(ctx);
+ }
+
+ vo->probing = old_probing;
+
+ // If we've reached this point, then none of the contexts matched the name
+ // requested, or the backend creation failed for all of them.
+ if (!vo->probing)
+ MP_ERR(vo, "Failed initializing any suitable GPU context!\n");
+ return NULL;
+}
+
+void ra_ctx_destroy(struct ra_ctx **ctx_ptr)
+{
+ struct ra_ctx *ctx = *ctx_ptr;
+ if (!ctx)
+ return;
+
+ if (ctx->spirv && ctx->spirv->fns->uninit)
+ ctx->spirv->fns->uninit(ctx);
+
+ ctx->fns->uninit(ctx);
+ talloc_free(ctx);
+
+ *ctx_ptr = NULL;
+}
diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h
new file mode 100644
index 0000000..78c0441
--- /dev/null
+++ b/video/out/gpu/context.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "video/out/vo.h"
+
+#include "config.h"
+#include "ra.h"
+
+struct ra_ctx_opts {
+ int allow_sw; // allow software renderers
+ int want_alpha; // create an alpha framebuffer if possible
+ int debug; // enable debugging layers/callbacks etc.
+ bool probing; // the backend was auto-probed
+ int swapchain_depth; // max number of images to render ahead
+};
+
+struct ra_ctx {
+ struct vo *vo;
+ struct ra *ra;
+ struct mpv_global *global;
+ struct mp_log *log;
+
+ struct ra_ctx_opts opts;
+ const struct ra_ctx_fns *fns;
+ struct ra_swapchain *swapchain;
+ struct spirv_compiler *spirv;
+
+ void *priv;
+};
+
+// The functions that make up a ra_ctx.
+struct ra_ctx_fns {
+ const char *type; // API type (for --gpu-api)
+ const char *name; // name (for --gpu-context)
+
+ // Resize the window, or create a new window if there isn't one yet.
+ // Currently, there is an unfortunate interaction with ctx->vo, and
+ // display size etc. are determined by it.
+ bool (*reconfig)(struct ra_ctx *ctx);
+
+ // This behaves exactly like vo_driver.control().
+ int (*control)(struct ra_ctx *ctx, int *events, int request, void *arg);
+
+ // These behave exactly like vo_driver.wakeup/wait_events. They are
+ // optional.
+ void (*wakeup)(struct ra_ctx *ctx);
+ void (*wait_events)(struct ra_ctx *ctx, int64_t until_time_us);
+
+ // Initialize/destroy the 'struct ra' and possibly the underlying VO backend.
+ // Not normally called by the user of the ra_ctx.
+ bool (*init)(struct ra_ctx *ctx);
+ void (*uninit)(struct ra_ctx *ctx);
+};
+
+// Extra struct for the swapchain-related functions so they can be easily
+// inherited from helpers.
+struct ra_swapchain {
+ struct ra_ctx *ctx;
+ struct priv *priv;
+ const struct ra_swapchain_fns *fns;
+};
+
+// Represents a framebuffer / render target
+struct ra_fbo {
+ struct ra_tex *tex;
+ bool flip; // rendering needs to be inverted
+};
+
+struct ra_swapchain_fns {
+ // Gets the current framebuffer depth in bits (0 if unknown). Optional.
+ int (*color_depth)(struct ra_swapchain *sw);
+
+ // Retrieves a screenshot of the framebuffer. Optional.
+ struct mp_image *(*screenshot)(struct ra_swapchain *sw);
+
+ // Called when rendering starts. Returns NULL on failure. This must be
+ // followed by submit_frame, to submit the rendered frame. This function
+ // can also fail sporadically, and such errors should be ignored unless
+ // they persist.
+ bool (*start_frame)(struct ra_swapchain *sw, struct ra_fbo *out_fbo);
+
+ // Present the frame. Issued in lockstep with start_frame, with rendering
+ // commands in between. The `frame` is just there for timing data, for
+ // swapchains smart enough to do something with it.
+ bool (*submit_frame)(struct ra_swapchain *sw, const struct vo_frame *frame);
+
+ // Performs a buffer swap. This blocks for as long as necessary to meet
+ // params.swapchain_depth, or until the next vblank (for vsynced contexts)
+ void (*swap_buffers)(struct ra_swapchain *sw);
+};
+
+// Create and destroy a ra_ctx. This also takes care of creating and destroying
+// the underlying `struct ra`, and perhaps the underlying VO backend.
+struct ra_ctx *ra_ctx_create(struct vo *vo, const char *context_type,
+ const char *context_name, struct ra_ctx_opts opts);
+void ra_ctx_destroy(struct ra_ctx **ctx);
+
+struct m_option;
+int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, struct bstr param);
+int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, struct bstr param);
diff --git a/video/out/opengl/d3d11_helpers.c b/video/out/gpu/d3d11_helpers.c
index d9b7fc2..b96b03a 100644
--- a/video/out/opengl/d3d11_helpers.c
+++ b/video/out/gpu/d3d11_helpers.c
@@ -46,6 +46,8 @@ static int get_feature_levels(int max_fl, int min_fl,
const D3D_FEATURE_LEVEL **out)
{
static const D3D_FEATURE_LEVEL levels[] = {
+ D3D_FEATURE_LEVEL_12_1,
+ D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
@@ -70,7 +72,7 @@ static int get_feature_levels(int max_fl, int min_fl,
return len;
}
-static HRESULT create_device(struct mp_log *log, bool warp, bool bgra,
+static HRESULT create_device(struct mp_log *log, bool warp, bool debug,
int max_fl, int min_fl, ID3D11Device **dev)
{
const D3D_FEATURE_LEVEL *levels;
@@ -82,7 +84,7 @@ static HRESULT create_device(struct mp_log *log, bool warp, bool bgra,
D3D_DRIVER_TYPE type = warp ? D3D_DRIVER_TYPE_WARP
: D3D_DRIVER_TYPE_HARDWARE;
- UINT flags = bgra ? D3D11_CREATE_DEVICE_BGRA_SUPPORT : 0;
+ UINT flags = debug ? D3D11_CREATE_DEVICE_DEBUG : 0;
return pD3D11CreateDevice(NULL, type, NULL, flags, levels, levels_len,
D3D11_SDK_VERSION, dev, NULL, NULL);
}
@@ -95,7 +97,6 @@ bool mp_d3d11_create_present_device(struct mp_log *log,
ID3D11Device **dev_out)
{
bool warp = opts->force_warp;
- bool bgra = true;
int max_fl = opts->max_feature_level;
int min_fl = opts->min_feature_level;
ID3D11Device *dev = NULL;
@@ -116,25 +117,27 @@ bool mp_d3d11_create_present_device(struct mp_log *log,
max_fl = max_fl ? max_fl : D3D_FEATURE_LEVEL_11_0;
min_fl = min_fl ? min_fl : D3D_FEATURE_LEVEL_9_1;
- hr = create_device(log, warp, bgra, max_fl, min_fl, &dev);
+ hr = create_device(log, warp, opts->debug, max_fl, min_fl, &dev);
if (SUCCEEDED(hr))
break;
- // BGRA is recommended, but FL 10_0 hardware may not support it
- if (bgra) {
- mp_dbg(log, "Failed to create D3D device with BGRA support\n");
- bgra = false;
+ // Trying to create a D3D_FEATURE_LEVEL_12_0 device on Windows 8.1 or
+ // below will not succeed. Try an 11_1 device.
+ if (max_fl >= D3D_FEATURE_LEVEL_12_0 &&
+ min_fl <= D3D_FEATURE_LEVEL_11_1)
+ {
+ mp_dbg(log, "Failed to create 12_0+ device, trying 11_1\n");
+ max_fl = D3D_FEATURE_LEVEL_11_1;
continue;
}
// Trying to create a D3D_FEATURE_LEVEL_11_1 device on Windows 7
- // without the platform update will not succeed. Try a 11_0 device.
+ // without the platform update will not succeed. Try an 11_0 device.
if (max_fl >= D3D_FEATURE_LEVEL_11_1 &&
min_fl <= D3D_FEATURE_LEVEL_11_0)
{
mp_dbg(log, "Failed to create 11_1+ device, trying 11_0\n");
max_fl = D3D_FEATURE_LEVEL_11_0;
- bgra = true;
continue;
}
@@ -144,7 +147,6 @@ bool mp_d3d11_create_present_device(struct mp_log *log,
warp = true;
max_fl = opts->max_feature_level;
min_fl = opts->min_feature_level;
- bgra = true;
continue;
}
@@ -179,11 +181,13 @@ bool mp_d3d11_create_present_device(struct mp_log *log,
(((unsigned)selected_level) >> 8) & 0xf);
char *dev_name = mp_to_utf8(NULL, desc.Description);
- mp_verbose(log, "Device: %s\n"
- "VendorId: 0x%04d\n"
- "DeviceId: 0x%04d\n"
+ mp_verbose(log, "Device Name: %s\n"
+ "Device ID: %04x:%04x (rev %02x)\n"
+ "Subsystem ID: %04x:%04x\n"
"LUID: %08lx%08lx\n",
- dev_name, desc.VendorId, desc.DeviceId,
+ dev_name,
+ desc.VendorId, desc.DeviceId, desc.Revision,
+ LOWORD(desc.SubSysId), HIWORD(desc.SubSysId),
desc.AdapterLuid.HighPart, desc.AdapterLuid.LowPart);
talloc_free(dev_name);
@@ -381,3 +385,84 @@ done:
SAFE_RELEASE(dxgi_dev);
return success;
}
+
+struct mp_image *mp_d3d11_screenshot(IDXGISwapChain *swapchain)
+{
+ ID3D11Device *dev = NULL;
+ ID3D11DeviceContext *ctx = NULL;
+ ID3D11Texture2D *frontbuffer = NULL;
+ ID3D11Texture2D *staging = NULL;
+ struct mp_image *img = NULL;
+ HRESULT hr;
+
+ // Validate the swap chain. This screenshot method will only work on DXGI
+ // 1.2+ flip/sequential swap chains. It's probably not possible at all with
+ // discard swap chains, since by definition, the backbuffer contents is
+ // discarded on Present().
+ DXGI_SWAP_CHAIN_DESC scd;
+ hr = IDXGISwapChain_GetDesc(swapchain, &scd);
+ if (FAILED(hr))
+ goto done;
+ if (scd.SwapEffect != DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL)
+ goto done;
+
+ // Get the last buffer that was presented with Present(). This should be
+ // the n-1th buffer for a swap chain of length n.
+ hr = IDXGISwapChain_GetBuffer(swapchain, scd.BufferCount - 1,
+ &IID_ID3D11Texture2D, (void**)&frontbuffer);
+ if (FAILED(hr))
+ goto done;
+
+ ID3D11Texture2D_GetDevice(frontbuffer, &dev);
+ ID3D11Device_GetImmediateContext(dev, &ctx);
+
+ D3D11_TEXTURE2D_DESC td;
+ ID3D11Texture2D_GetDesc(frontbuffer, &td);
+ if (td.SampleDesc.Count > 1)
+ goto done;
+
+ // Validate the backbuffer format and convert to an mpv IMGFMT
+ enum mp_imgfmt fmt;
+ switch (td.Format) {
+ case DXGI_FORMAT_B8G8R8A8_UNORM: fmt = IMGFMT_BGR0; break;
+ case DXGI_FORMAT_R8G8B8A8_UNORM: fmt = IMGFMT_RGB0; break;
+ default:
+ goto done;
+ }
+
+ // Create a staging texture based on the frontbuffer with CPU access
+ td.BindFlags = 0;
+ td.MiscFlags = 0;
+ td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ td.Usage = D3D11_USAGE_STAGING;
+ hr = ID3D11Device_CreateTexture2D(dev, &td, 0, &staging);
+ if (FAILED(hr))
+ goto done;
+
+ ID3D11DeviceContext_CopyResource(ctx, (ID3D11Resource*)staging,
+ (ID3D11Resource*)frontbuffer);
+
+ // Attempt to map the staging texture to CPU-accessible memory
+ D3D11_MAPPED_SUBRESOURCE lock;
+ hr = ID3D11DeviceContext_Map(ctx, (ID3D11Resource*)staging, 0,
+ D3D11_MAP_READ, 0, &lock);
+ if (FAILED(hr))
+ goto done;
+
+ img = mp_image_alloc(fmt, td.Width, td.Height);
+ if (!img)
+ return NULL;
+ for (int i = 0; i < td.Height; i++) {
+ memcpy(img->planes[0] + img->stride[0] * i,
+ (char*)lock.pData + lock.RowPitch * i, td.Width * 4);
+ }
+
+ ID3D11DeviceContext_Unmap(ctx, (ID3D11Resource*)staging, 0);
+
+done:
+ SAFE_RELEASE(frontbuffer);
+ SAFE_RELEASE(staging);
+ SAFE_RELEASE(ctx);
+ SAFE_RELEASE(dev);
+ return img;
+}
diff --git a/video/out/opengl/d3d11_helpers.h b/video/out/gpu/d3d11_helpers.h
index f34d1d4..481c183 100644
--- a/video/out/opengl/d3d11_helpers.h
+++ b/video/out/gpu/d3d11_helpers.h
@@ -23,7 +23,15 @@
#include <d3d11.h>
#include <dxgi1_2.h>
+#include "video/mp_image.h"
+
+#define D3D_FEATURE_LEVEL_12_0 (0xc000)
+#define D3D_FEATURE_LEVEL_12_1 (0xc100)
+
struct d3d11_device_opts {
+ // Enable the debug layer (D3D11_CREATE_DEVICE_DEBUG)
+ bool debug;
+
// Allow a software (WARP) adapter. Note, sometimes a software adapter will
// be used even when allow_warp is false. This is because, on Windows 8 and
// up, if there are no hardware adapters, Windows will pretend the WARP
@@ -70,4 +78,6 @@ bool mp_d3d11_create_swapchain(ID3D11Device *dev, struct mp_log *log,
struct d3d11_swapchain_opts *opts,
IDXGISwapChain **swapchain_out);
+struct mp_image *mp_d3d11_screenshot(IDXGISwapChain *swapchain);
+
#endif
diff --git a/video/out/opengl/hwdec.c b/video/out/gpu/hwdec.c
index 5fbc1aa..5284116 100644
--- a/video/out/opengl/hwdec.c
+++ b/video/out/gpu/hwdec.c
@@ -34,19 +34,16 @@ extern const struct ra_hwdec_driver ra_hwdec_d3d11egl;
extern const struct ra_hwdec_driver ra_hwdec_d3d11eglrgb;
extern const struct ra_hwdec_driver ra_hwdec_dxva2gldx;
extern const struct ra_hwdec_driver ra_hwdec_dxva2;
+extern const struct ra_hwdec_driver ra_hwdec_d3d11va;
extern const struct ra_hwdec_driver ra_hwdec_cuda;
+extern const struct ra_hwdec_driver ra_hwdec_cuda_nvdec;
extern const struct ra_hwdec_driver ra_hwdec_rpi_overlay;
+extern const struct ra_hwdec_driver ra_hwdec_drmprime_drm;
-static const struct ra_hwdec_driver *const mpgl_hwdec_drivers[] = {
+const struct ra_hwdec_driver *const ra_hwdec_drivers[] = {
#if HAVE_VAAPI_EGL
&ra_hwdec_vaegl,
#endif
-#if HAVE_VAAPI_GLX
- &ra_hwdec_vaglx,
-#endif
-#if HAVE_VDPAU_GL_X11
- &ra_hwdec_vdpau,
-#endif
#if HAVE_VIDEOTOOLBOX_GL || HAVE_IOS_GL
&ra_hwdec_videotoolbox,
#endif
@@ -56,6 +53,9 @@ static const struct ra_hwdec_driver *const mpgl_hwdec_drivers[] = {
#if HAVE_D3D9_HWACCEL
&ra_hwdec_dxva2egl,
#endif
+ #if HAVE_D3D11
+ &ra_hwdec_d3d11va,
+ #endif
#endif
#if HAVE_GL_DXINTEROP_D3D9
&ra_hwdec_dxva2gldx,
@@ -63,17 +63,24 @@ static const struct ra_hwdec_driver *const mpgl_hwdec_drivers[] = {
#if HAVE_CUDA_HWACCEL
&ra_hwdec_cuda,
#endif
+#if HAVE_VDPAU_GL_X11
+ &ra_hwdec_vdpau,
+#endif
#if HAVE_RPI
&ra_hwdec_rpi_overlay,
#endif
+#if HAVE_DRMPRIME && HAVE_DRM
+ &ra_hwdec_drmprime_drm,
+#endif
+
NULL
};
-static struct ra_hwdec *load_hwdec_driver(struct mp_log *log, struct ra *ra,
- struct mpv_global *global,
- struct mp_hwdec_devices *devs,
- const struct ra_hwdec_driver *drv,
- bool is_auto)
+struct ra_hwdec *ra_hwdec_load_driver(struct ra *ra, struct mp_log *log,
+ struct mpv_global *global,
+ struct mp_hwdec_devices *devs,
+ const struct ra_hwdec_driver *drv,
+ bool is_auto)
{
struct ra_hwdec *hwdec = talloc(NULL, struct ra_hwdec);
*hwdec = (struct ra_hwdec) {
@@ -94,81 +101,31 @@ static struct ra_hwdec *load_hwdec_driver(struct mp_log *log, struct ra *ra,
return hwdec;
}
-struct ra_hwdec *ra_hwdec_load_api(struct mp_log *log, struct ra *ra,
- struct mpv_global *g,
- struct mp_hwdec_devices *devs,
- enum hwdec_type api)
-{
- bool is_auto = HWDEC_IS_AUTO(api);
- for (int n = 0; mpgl_hwdec_drivers[n]; n++) {
- const struct ra_hwdec_driver *drv = mpgl_hwdec_drivers[n];
- if ((is_auto || api == drv->api) && !drv->testing_only) {
- struct ra_hwdec *r = load_hwdec_driver(log, ra, g, devs, drv, is_auto);
- if (r)
- return r;
- }
- }
- return NULL;
-}
-
-// Load by option name.
-struct ra_hwdec *ra_hwdec_load(struct mp_log *log, struct ra *ra,
- struct mpv_global *g,
- struct mp_hwdec_devices *devs,
- const char *name)
-{
- int g_hwdec_api;
- mp_read_option_raw(g, "hwdec", &m_option_type_choice, &g_hwdec_api);
- if (!name || !name[0])
- name = m_opt_choice_str(mp_hwdec_names, g_hwdec_api);
-
- int api_id = HWDEC_NONE;
- for (int n = 0; mp_hwdec_names[n].name; n++) {
- if (name && strcmp(mp_hwdec_names[n].name, name) == 0)
- api_id = mp_hwdec_names[n].value;
- }
-
- for (int n = 0; mpgl_hwdec_drivers[n]; n++) {
- const struct ra_hwdec_driver *drv = mpgl_hwdec_drivers[n];
- if (name && strcmp(drv->name, name) == 0) {
- struct ra_hwdec *r = load_hwdec_driver(log, ra, g, devs, drv, false);
- if (r)
- return r;
- }
- }
-
- return ra_hwdec_load_api(log, ra, g, devs, api_id);
-}
-
int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param)
{
bool help = bstr_equals0(param, "help");
if (help)
mp_info(log, "Available hwdecs:\n");
- for (int n = 0; mpgl_hwdec_drivers[n]; n++) {
- const struct ra_hwdec_driver *drv = mpgl_hwdec_drivers[n];
- const char *api_name = m_opt_choice_str(mp_hwdec_names, drv->api);
+ for (int n = 0; ra_hwdec_drivers[n]; n++) {
+ const struct ra_hwdec_driver *drv = ra_hwdec_drivers[n];
if (help) {
- mp_info(log, " %s [%s]\n", drv->name, api_name);
- } else if (bstr_equals0(param, drv->name) ||
- bstr_equals0(param, api_name))
- {
+ mp_info(log, " %s\n", drv->name);
+ } else if (bstr_equals0(param, drv->name)) {
return 1;
}
}
if (help) {
- mp_info(log, " auto (loads best)\n"
- " (other --hwdec values)\n"
- "Setting an empty string means use --hwdec.\n");
+ mp_info(log, " auto (behavior depends on context)\n"
+ " all (load all hwdecs)\n"
+ " no (do not load any and block loading on demand)\n");
return M_OPT_EXIT;
}
if (!param.len)
return 1; // "" is treated specially
- for (int n = 0; mp_hwdec_names[n].name; n++) {
- if (bstr_equals0(param, mp_hwdec_names[n].name))
- return 1;
- }
+ if (bstr_equals0(param, "all") || bstr_equals0(param, "auto") ||
+ bstr_equals0(param, "no"))
+ return 1;
mp_fatal(log, "No hwdec backend named '%.*s' found!\n", BSTR_P(param));
return M_OPT_INVALID;
}
diff --git a/video/out/opengl/hwdec.h b/video/out/gpu/hwdec.h
index 20bbaae..258ab88 100644
--- a/video/out/opengl/hwdec.h
+++ b/video/out/gpu/hwdec.h
@@ -72,17 +72,14 @@ struct ra_hwdec_mapper_driver {
};
struct ra_hwdec_driver {
- // Name of the interop backend. This is used for informational purposes only.
+ // Name of the interop backend. This is used for informational purposes and
+ // for use with debugging options.
const char *name;
// Used to create ra_hwdec.priv.
size_t priv_size;
- // Used to explicitly request a specific API.
- enum hwdec_type api;
// One of the hardware surface IMGFMT_ that must be passed to map_image later.
// Terminated with a 0 entry. (Extend the array size as needed.)
const int imgfmts[3];
- // Dosn't load this unless requested by name.
- bool testing_only;
// Create the hwdec device. It must add it to hw->devs, if applicable.
int (*init)(struct ra_hwdec *hw);
@@ -104,15 +101,13 @@ struct ra_hwdec_driver {
struct mp_rect *src, struct mp_rect *dst, bool newframe);
};
-struct ra_hwdec *ra_hwdec_load_api(struct mp_log *log, struct ra *ra,
- struct mpv_global *g,
- struct mp_hwdec_devices *devs,
- enum hwdec_type api);
+extern const struct ra_hwdec_driver *const ra_hwdec_drivers[];
-struct ra_hwdec *ra_hwdec_load(struct mp_log *log, struct ra *ra,
- struct mpv_global *g,
- struct mp_hwdec_devices *devs,
- const char *name);
+struct ra_hwdec *ra_hwdec_load_driver(struct ra *ra, struct mp_log *log,
+ struct mpv_global *global,
+ struct mp_hwdec_devices *devs,
+ const struct ra_hwdec_driver *drv,
+ bool is_auto);
int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param);
diff --git a/video/out/opengl/lcms.c b/video/out/gpu/lcms.c
index 8747ae6..3552351 100644
--- a/video/out/opengl/lcms.c
+++ b/video/out/gpu/lcms.c
@@ -236,7 +236,7 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms,
}
// Otherwise, warn the user and generate the profile as usual
- MP_WARN(p, "Video contained an invalid ICC profile! Ignoring..\n");
+ MP_WARN(p, "Video contained an invalid ICC profile! Ignoring...\n");
}
// The input profile for the transformation is dependent on the video
diff --git a/video/out/opengl/lcms.h b/video/out/gpu/lcms.h
index 35bbd61..35bbd61 100644
--- a/video/out/opengl/lcms.h
+++ b/video/out/gpu/lcms.h
diff --git a/video/out/opengl/osd.c b/video/out/gpu/osd.c
index f7c325d..317deb6 100644
--- a/video/out/opengl/osd.c
+++ b/video/out/gpu/osd.c
@@ -47,7 +47,6 @@ static const struct ra_renderpass_input vertex_vao[] = {
{"position", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, position)},
{"texcoord" , RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord)},
{"ass_color", RA_VARTYPE_BYTE_UNORM, 4, 1, offsetof(struct vertex, ass_color)},
- {0}
};
struct mpgl_osd_part {
@@ -231,8 +230,6 @@ bool mpgl_osd_draw_prepare(struct mpgl_osd *ctx, int index,
abort();
}
- gl_sc_set_vertex_format(sc, vertex_vao, sizeof(struct vertex));
-
return true;
}
@@ -256,8 +253,8 @@ static void write_quad(struct vertex *va, struct gl_transform t,
static void generate_verts(struct mpgl_osd_part *part, struct gl_transform t)
{
- int num_vertices = part->num_subparts * 6;
- MP_TARRAY_GROW(part, part->vertices, part->num_vertices + num_vertices);
+ MP_TARRAY_GROW(part, part->vertices,
+ part->num_vertices + part->num_subparts * 6);
for (int n = 0; n < part->num_subparts; n++) {
struct sub_bitmap *b = &part->subparts[n];
@@ -269,13 +266,13 @@ static void generate_verts(struct mpgl_osd_part *part, struct gl_transform t)
uint8_t color[4] = { c >> 24, (c >> 16) & 0xff,
(c >> 8) & 0xff, 255 - (c & 0xff) };
- write_quad(&va[n * 6], t,
+ write_quad(va, t,
b->x, b->y, b->x + b->dw, b->y + b->dh,
b->src_x, b->src_y, b->src_x + b->w, b->src_y + b->h,
part->w, part->h, color);
- }
- part->num_vertices += num_vertices;
+ part->num_vertices += 6;
+ }
}
// number of screen divisions per axis (x=0, y=1) for the current 3D mode
@@ -291,7 +288,7 @@ static void get_3d_side_by_side(int stereo_mode, int div[2])
}
void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index,
- struct gl_shader_cache *sc, struct fbodst target)
+ struct gl_shader_cache *sc, struct ra_fbo fbo)
{
struct mpgl_osd_part *part = ctx->parts[index];
@@ -303,7 +300,7 @@ void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index,
for (int x = 0; x < div[0]; x++) {
for (int y = 0; y < div[1]; y++) {
struct gl_transform t;
- gl_transform_ortho_fbodst(&t, target);
+ gl_transform_ortho_fbo(&t, fbo);
float a_x = ctx->osd_res.w * x;
float a_y = ctx->osd_res.h * y;
@@ -317,7 +314,8 @@ void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index,
const int *factors = &blend_factors[part->format][0];
gl_sc_blend(sc, factors[0], factors[1], factors[2], factors[3]);
- gl_sc_dispatch_draw(sc, target.tex, part->vertices, part->num_vertices);
+ gl_sc_dispatch_draw(sc, fbo.tex, vertex_vao, MP_ARRAY_SIZE(vertex_vao),
+ sizeof(struct vertex), part->vertices, part->num_vertices);
}
static void set_res(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mode)
diff --git a/video/out/opengl/osd.h b/video/out/gpu/osd.h
index 6c2b886..00fbc49 100644
--- a/video/out/opengl/osd.h
+++ b/video/out/gpu/osd.h
@@ -18,7 +18,7 @@ void mpgl_osd_resize(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mod
bool mpgl_osd_draw_prepare(struct mpgl_osd *ctx, int index,
struct gl_shader_cache *sc);
void mpgl_osd_draw_finish(struct mpgl_osd *ctx, int index,
- struct gl_shader_cache *sc, struct fbodst target);
+ struct gl_shader_cache *sc, struct ra_fbo fbo);
bool mpgl_osd_check_change(struct mpgl_osd *ctx, struct mp_osd_res *res,
double pts);
diff --git a/video/out/opengl/ra.c b/video/out/gpu/ra.c
index 208507d..fdb20fe 100644
--- a/video/out/opengl/ra.c
+++ b/video/out/gpu/ra.c
@@ -71,7 +71,7 @@ static struct ra_renderpass_input *dup_inputs(void *ta_parent,
}
// Return a newly allocated deep-copy of params.
-struct ra_renderpass_params *ra_render_pass_params_copy(void *ta_parent,
+struct ra_renderpass_params *ra_renderpass_params_copy(void *ta_parent,
const struct ra_renderpass_params *params)
{
struct ra_renderpass_params *res = talloc_ptrtype(ta_parent, res);
@@ -86,6 +86,65 @@ struct ra_renderpass_params *ra_render_pass_params_copy(void *ta_parent,
return res;
};
+struct glsl_fmt {
+ enum ra_ctype ctype;
+ int num_components;
+ int component_depth[4];
+ const char *glsl_format;
+};
+
+// List taken from the GLSL specification, sans snorm and sint formats
+static const struct glsl_fmt ra_glsl_fmts[] = {
+ {RA_CTYPE_FLOAT, 1, {16}, "r16f"},
+ {RA_CTYPE_FLOAT, 1, {32}, "r32f"},
+ {RA_CTYPE_FLOAT, 2, {16, 16}, "rg16f"},
+ {RA_CTYPE_FLOAT, 2, {32, 32}, "rg32f"},
+ {RA_CTYPE_FLOAT, 4, {16, 16, 16, 16}, "rgba16f"},
+ {RA_CTYPE_FLOAT, 4, {32, 32, 32, 32}, "rgba32f"},
+ {RA_CTYPE_FLOAT, 3, {11, 11, 10}, "r11f_g11f_b10f"},
+
+ {RA_CTYPE_UNORM, 1, {8}, "r8"},
+ {RA_CTYPE_UNORM, 1, {16}, "r16"},
+ {RA_CTYPE_UNORM, 2, {8, 8}, "rg8"},
+ {RA_CTYPE_UNORM, 2, {16, 16}, "rg16"},
+ {RA_CTYPE_UNORM, 4, {8, 8, 8, 8}, "rgba8"},
+ {RA_CTYPE_UNORM, 4, {16, 16, 16, 16}, "rgba16"},
+ {RA_CTYPE_UNORM, 4, {10, 10, 10, 2}, "rgb10_a2"},
+
+ {RA_CTYPE_UINT, 1, {8}, "r8ui"},
+ {RA_CTYPE_UINT, 1, {16}, "r16ui"},
+ {RA_CTYPE_UINT, 1, {32}, "r32ui"},
+ {RA_CTYPE_UINT, 2, {8, 8}, "rg8ui"},
+ {RA_CTYPE_UINT, 2, {16, 16}, "rg16ui"},
+ {RA_CTYPE_UINT, 2, {32, 32}, "rg32ui"},
+ {RA_CTYPE_UINT, 4, {8, 8, 8, 8}, "rgba8ui"},
+ {RA_CTYPE_UINT, 4, {16, 16, 16, 16}, "rgba16ui"},
+ {RA_CTYPE_UINT, 4, {32, 32, 32, 32}, "rgba32ui"},
+ {RA_CTYPE_UINT, 4, {10, 10, 10, 2}, "rgb10_a2ui"},
+};
+
+const char *ra_fmt_glsl_format(const struct ra_format *fmt)
+{
+ for (int n = 0; n < MP_ARRAY_SIZE(ra_glsl_fmts); n++) {
+ const struct glsl_fmt *gfmt = &ra_glsl_fmts[n];
+
+ if (fmt->ctype != gfmt->ctype)
+ continue;
+ if (fmt->num_components != gfmt->num_components)
+ continue;
+
+ for (int i = 0; i < fmt->num_components; i++) {
+ if (fmt->component_depth[i] != gfmt->component_depth[i])
+ goto next_fmt;
+ }
+
+ return gfmt->glsl_format;
+
+next_fmt: ; // equivalent to `continue`
+ }
+
+ return NULL;
+}
// Return whether this is a tightly packed format with no external padding and
// with the same bit size/depth in all components, and the shader returns
diff --git a/video/out/opengl/ra.h b/video/out/gpu/ra.h
index 46a69f2..934e5db 100644
--- a/video/out/opengl/ra.h
+++ b/video/out/gpu/ra.h
@@ -26,6 +26,9 @@ struct ra {
// time.
size_t max_shmem;
+ // Maximum push constant size. Set by the RA backend at init time.
+ size_t max_pushc_size;
+
// Set of supported texture formats. Must be added by RA backend at init time.
// If there are equivalent formats with different caveats, the preferred
// formats should have a lower index. (E.g. GLES3 should put rg8 before la.)
@@ -47,8 +50,9 @@ enum {
RA_CAP_BUF_RO = 1 << 5, // supports RA_VARTYPE_BUF_RO
RA_CAP_BUF_RW = 1 << 6, // supports RA_VARTYPE_BUF_RW
RA_CAP_NESTED_ARRAY = 1 << 7, // supports nested arrays
- RA_CAP_SHARED_BINDING = 1 << 8, // sampler/image/buffer namespaces are disjoint
- RA_CAP_GLOBAL_UNIFORM = 1 << 9, // supports using "naked" uniforms (not UBO)
+ RA_CAP_GLOBAL_UNIFORM = 1 << 8, // supports using "naked" uniforms (not UBO)
+ RA_CAP_GATHER = 1 << 9, // supports textureGather in GLSL
+ RA_CAP_FRAGCOORD = 1 << 10, // supports reading from gl_FragCoord
};
enum ra_ctype {
@@ -85,6 +89,10 @@ struct ra_format {
// shader representation is given by the special_imgfmt_desc pointer.
int special_imgfmt;
const struct ra_imgfmt_desc *special_imgfmt_desc;
+
+ // This gives the GLSL image format corresponding to the format, if any.
+ // (e.g. rgba16ui)
+ const char *glsl_format;
};
struct ra_tex_params {
@@ -139,13 +147,14 @@ struct ra_tex_upload_params {
ptrdiff_t stride; // The size of a horizontal line in bytes (*not* texels!)
};
-// Buffer type hint. Setting this may result in more or less efficient
-// operation, although it shouldn't technically prohibit anything
+// Buffer usage type. This restricts what types of operations may be performed
+// on a buffer.
enum ra_buf_type {
RA_BUF_TYPE_INVALID,
RA_BUF_TYPE_TEX_UPLOAD, // texture upload buffer (pixel buffer object)
RA_BUF_TYPE_SHADER_STORAGE, // shader buffer (SSBO), for RA_VARTYPE_BUF_RW
RA_BUF_TYPE_UNIFORM, // uniform buffer (UBO), for RA_VARTYPE_BUF_RO
+ RA_BUF_TYPE_VERTEX, // not publicly usable (RA-internal usage)
};
struct ra_buf_params {
@@ -202,8 +211,8 @@ struct ra_renderpass_input {
// RA_VARTYPE_IMG_W: image unit
// RA_VARTYPE_BUF_* buffer binding point
// Other uniforms: unused
- // If RA_CAP_SHARED_BINDING is set, these may only be unique per input type.
- // Otherwise, these must be unique for all input values.
+ // Bindings must be unique within each namespace, as specified by
+ // desc_namespace()
int binding;
};
@@ -244,6 +253,7 @@ struct ra_renderpass_params {
// Uniforms, including texture/sampler inputs.
struct ra_renderpass_input *inputs;
int num_inputs;
+ size_t push_constants_size; // must be <= ra.max_pushc_size and a multiple of 4
// Highly implementation-specific byte array storing a compiled version
// of the program. Can be used to speed up shader compilation. A backend
@@ -281,7 +291,7 @@ struct ra_renderpass_params {
const char *compute_shader;
};
-struct ra_renderpass_params *ra_render_pass_params_copy(void *ta_parent,
+struct ra_renderpass_params *ra_renderpass_params_copy(void *ta_parent,
const struct ra_renderpass_params *params);
// Conflates the following typical GPU API concepts:
@@ -316,6 +326,7 @@ struct ra_renderpass_run_params {
// even if they do not change.
struct ra_renderpass_input_val *values;
int num_values;
+ void *push_constants; // must be set if params.push_constants_size > 0
// --- pass->params.type==RA_RENDERPASS_TYPE_RASTER only
@@ -369,10 +380,10 @@ struct ra_fns {
void (*buf_destroy)(struct ra *ra, struct ra_buf *buf);
- // Update the contents of a buffer, starting at a given offset and up to a
- // given size, with the contents of *data. This is an extremely common
- // operation. Calling this while the buffer is considered "in use" is an
- // error. (See: buf_poll)
+ // Update the contents of a buffer, starting at a given offset (*must* be a
+ // multiple of 4) and up to a given size, with the contents of *data. This
+ // is an extremely common operation. Calling this while the buffer is
+ // considered "in use" is an error. (See: buf_poll)
void (*buf_update)(struct ra *ra, struct ra_buf *buf, ptrdiff_t offset,
const void *data, size_t size);
@@ -386,6 +397,15 @@ struct ra_fns {
// but must be implemented if RA_CAP_BUF_RO is supported.
struct ra_layout (*uniform_layout)(struct ra_renderpass_input *inp);
+ // Returns the layout requirements of a push constant element. Optional,
+ // but must be implemented if ra.max_pushc_size > 0.
+ struct ra_layout (*push_constant_layout)(struct ra_renderpass_input *inp);
+
+ // Returns an abstract namespace index for a given renderpass input type.
+ // This will always be a value >= 0 and < RA_VARTYPE_COUNT. This is used to
+ // figure out which inputs may share the same value of `binding`.
+ int (*desc_namespace)(enum ra_vartype type);
+
// Clear the dst with the given color (rgba) and within the given scissor.
// dst must have dst->params.render_dst==true. Content outside of the
// scissor is preserved.
@@ -436,9 +456,6 @@ struct ra_fns {
// delayed by a few frames. When no value is available, this returns 0.
uint64_t (*timer_stop)(struct ra *ra, ra_timer *timer);
- // Hint that possibly queued up commands should be sent to the GPU. Optional.
- void (*flush)(struct ra *ra);
-
// Associates a marker with any past error messages, for debugging
// purposes. Optional.
void (*debug_marker)(struct ra *ra, const char *msg);
@@ -483,6 +500,8 @@ struct ra_imgfmt_desc {
uint8_t components[4][4];
};
+const char *ra_fmt_glsl_format(const struct ra_format *fmt);
+
bool ra_get_imgfmt_desc(struct ra *ra, int imgfmt, struct ra_imgfmt_desc *out);
void ra_dump_tex_formats(struct ra *ra, int msgl);
diff --git a/video/out/opengl/shader_cache.c b/video/out/gpu/shader_cache.c
index 90a7576..6d0f370 100644
--- a/video/out/opengl/shader_cache.c
+++ b/video/out/gpu/shader_cache.c
@@ -14,7 +14,6 @@
#include "options/path.h"
#include "stream/stream.h"
#include "shader_cache.h"
-#include "formats.h"
#include "utils.h"
// Force cache flush if more than this number of shaders is created.
@@ -30,6 +29,7 @@ union uniform_val {
enum sc_uniform_type {
SC_UNIFORM_TYPE_GLOBAL = 0, // global uniform (RA_CAP_GLOBAL_UNIFORM)
SC_UNIFORM_TYPE_UBO = 1, // uniform buffer (RA_CAP_BUF_RO)
+ SC_UNIFORM_TYPE_PUSHC = 2, // push constant (ra.max_pushc_size)
};
struct sc_uniform {
@@ -38,7 +38,7 @@ struct sc_uniform {
const char *glsl_type;
union uniform_val v;
char *buffer_format;
- // for SC_UNIFORM_TYPE_UBO:
+ // for SC_UNIFORM_TYPE_UBO/PUSHC:
struct ra_layout layout;
size_t offset; // byte offset within the buffer
};
@@ -57,6 +57,7 @@ struct sc_entry {
struct timer_pool *timer;
struct ra_buf *ubo;
int ubo_index; // for ra_renderpass_input_val.index
+ void *pushc;
};
struct gl_shader_cache {
@@ -75,6 +76,7 @@ struct gl_shader_cache {
// Next binding point (texture unit, image unit, buffer binding, etc.)
// In OpenGL these are separate for each input type
int next_binding[RA_VARTYPE_COUNT];
+ bool next_uniform_dynamic;
struct ra_renderpass_params params;
@@ -88,6 +90,7 @@ struct gl_shader_cache {
int ubo_binding;
size_t ubo_size;
+ size_t pushc_size;
struct ra_renderpass_input_val *values;
int num_values;
@@ -105,8 +108,6 @@ struct gl_shader_cache {
struct mpv_global *global; // can be NULL
};
-static void gl_sc_reset(struct gl_shader_cache *sc);
-
struct gl_shader_cache *gl_sc_create(struct ra *ra, struct mpv_global *global,
struct mp_log *log)
{
@@ -121,8 +122,8 @@ struct gl_shader_cache *gl_sc_create(struct ra *ra, struct mpv_global *global,
}
// Reset the previous pass. This must be called after gl_sc_generate and before
-// starting a new shader.
-static void gl_sc_reset(struct gl_shader_cache *sc)
+// starting a new shader. It may also be called on errors.
+void gl_sc_reset(struct gl_shader_cache *sc)
{
sc->prelude_text.len = 0;
sc->header_text.len = 0;
@@ -132,8 +133,10 @@ static void gl_sc_reset(struct gl_shader_cache *sc)
sc->num_uniforms = 0;
sc->ubo_binding = 0;
sc->ubo_size = 0;
+ sc->pushc_size = 0;
for (int i = 0; i < RA_VARTYPE_COUNT; i++)
sc->next_binding[i] = 0;
+ sc->next_uniform_dynamic = false;
sc->current_shader = NULL;
sc->params = (struct ra_renderpass_params){0};
sc->needs_reset = false;
@@ -141,7 +144,7 @@ static void gl_sc_reset(struct gl_shader_cache *sc)
static void sc_flush_cache(struct gl_shader_cache *sc)
{
- MP_VERBOSE(sc, "flushing shader cache\n");
+ MP_DBG(sc, "flushing shader cache\n");
for (int n = 0; n < sc->num_entries; n++) {
struct sc_entry *e = sc->entries[n];
@@ -251,32 +254,59 @@ static struct sc_uniform *find_uniform(struct gl_shader_cache *sc,
static int gl_sc_next_binding(struct gl_shader_cache *sc, enum ra_vartype type)
{
- if (sc->ra->caps & RA_CAP_SHARED_BINDING) {
- return sc->next_binding[type]++;
- } else {
- return sc->next_binding[0]++;
- }
+ return sc->next_binding[sc->ra->fns->desc_namespace(type)]++;
}
-// Updates the UBO metadata for the given sc_uniform. Assumes sc_uniform->input
-// is already set. Also updates sc_uniform->type.
-static void update_ubo_params(struct gl_shader_cache *sc, struct sc_uniform *u)
+void gl_sc_uniform_dynamic(struct gl_shader_cache *sc)
{
- if (!(sc->ra->caps & RA_CAP_BUF_RO))
- return;
+ sc->next_uniform_dynamic = true;
+}
+
+// Updates the metadata for the given sc_uniform. Assumes sc_uniform->input
+// and glsl_type/buffer_format are already set.
+static void update_uniform_params(struct gl_shader_cache *sc, struct sc_uniform *u)
+{
+ bool dynamic = sc->next_uniform_dynamic;
+ sc->next_uniform_dynamic = false;
+
+ // Try not using push constants for "large" values like matrices, since
+ // this is likely to both exceed the VGPR budget as well as the pushc size
+ // budget
+ bool try_pushc = u->input.dim_m == 1 || dynamic;
+
+ // Attempt using push constants first
+ if (try_pushc && sc->ra->glsl_vulkan && sc->ra->max_pushc_size) {
+ struct ra_layout layout = sc->ra->fns->push_constant_layout(&u->input);
+ size_t offset = MP_ALIGN_UP(sc->pushc_size, layout.align);
+ // Push constants have limited size, so make sure we don't exceed this
+ size_t new_size = offset + layout.size;
+ if (new_size <= sc->ra->max_pushc_size) {
+ u->type = SC_UNIFORM_TYPE_PUSHC;
+ u->layout = layout;
+ u->offset = offset;
+ sc->pushc_size = new_size;
+ return;
+ }
+ }
- // Using UBOs with explicit layout(offset) like we do requires GLSL version
- // 440 or higher. In theory the UBO code can also use older versions, but
- // just try and avoid potential headaches. This also ensures they're only
- // used on drivers that are probably modern enough to actually support them
- // correctly.
- if (sc->ra->glsl_version < 440)
+ // Attempt using uniform buffer next. The GLSL version 440 check is due
+ // to explicit offsets on UBO entries. In theory we could leave away
+ // the offsets and support UBOs for older GL as well, but this is a nice
+ // safety net for driver bugs (and also rules out potentially buggy drivers)
+ // Also avoid UBOs for highly dynamic stuff since that requires synchronizing
+ // the UBO writes every frame
+ bool try_ubo = !(sc->ra->caps & RA_CAP_GLOBAL_UNIFORM) || !dynamic;
+ if (try_ubo && sc->ra->glsl_version >= 440 && (sc->ra->caps & RA_CAP_BUF_RO)) {
+ u->type = SC_UNIFORM_TYPE_UBO;
+ u->layout = sc->ra->fns->uniform_layout(&u->input);
+ u->offset = MP_ALIGN_UP(sc->ubo_size, u->layout.align);
+ sc->ubo_size = u->offset + u->layout.size;
return;
+ }
- u->type = SC_UNIFORM_TYPE_UBO;
- u->layout = sc->ra->fns->uniform_layout(&u->input);
- u->offset = MP_ALIGN_UP(sc->ubo_size, u->layout.align);
- sc->ubo_size = u->offset + u->layout.size;
+ // If all else fails, use global uniforms
+ assert(sc->ra->caps & RA_CAP_GLOBAL_UNIFORM);
+ u->type = SC_UNIFORM_TYPE_GLOBAL;
}
void gl_sc_uniform_texture(struct gl_shader_cache *sc, char *name,
@@ -337,7 +367,7 @@ void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, float f)
struct sc_uniform *u = find_uniform(sc, name);
u->input.type = RA_VARTYPE_FLOAT;
u->glsl_type = "float";
- update_ubo_params(sc, u);
+ update_uniform_params(sc, u);
u->v.f[0] = f;
}
@@ -346,7 +376,7 @@ void gl_sc_uniform_i(struct gl_shader_cache *sc, char *name, int i)
struct sc_uniform *u = find_uniform(sc, name);
u->input.type = RA_VARTYPE_INT;
u->glsl_type = "int";
- update_ubo_params(sc, u);
+ update_uniform_params(sc, u);
u->v.i[0] = i;
}
@@ -356,18 +386,18 @@ void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, float f[2])
u->input.type = RA_VARTYPE_FLOAT;
u->input.dim_v = 2;
u->glsl_type = "vec2";
- update_ubo_params(sc, u);
+ update_uniform_params(sc, u);
u->v.f[0] = f[0];
u->v.f[1] = f[1];
}
-void gl_sc_uniform_vec3(struct gl_shader_cache *sc, char *name, GLfloat f[3])
+void gl_sc_uniform_vec3(struct gl_shader_cache *sc, char *name, float f[3])
{
struct sc_uniform *u = find_uniform(sc, name);
u->input.type = RA_VARTYPE_FLOAT;
u->input.dim_v = 3;
u->glsl_type = "vec3";
- update_ubo_params(sc, u);
+ update_uniform_params(sc, u);
u->v.f[0] = f[0];
u->v.f[1] = f[1];
u->v.f[2] = f[2];
@@ -379,14 +409,14 @@ static void transpose2x2(float r[2 * 2])
}
void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
- bool transpose, GLfloat *v)
+ bool transpose, float *v)
{
struct sc_uniform *u = find_uniform(sc, name);
u->input.type = RA_VARTYPE_FLOAT;
u->input.dim_v = 2;
u->input.dim_m = 2;
u->glsl_type = "mat2";
- update_ubo_params(sc, u);
+ update_uniform_params(sc, u);
for (int n = 0; n < 4; n++)
u->v.f[n] = v[n];
if (transpose)
@@ -401,34 +431,20 @@ static void transpose3x3(float r[3 * 3])
}
void gl_sc_uniform_mat3(struct gl_shader_cache *sc, char *name,
- bool transpose, GLfloat *v)
+ bool transpose, float *v)
{
struct sc_uniform *u = find_uniform(sc, name);
u->input.type = RA_VARTYPE_FLOAT;
u->input.dim_v = 3;
u->input.dim_m = 3;
u->glsl_type = "mat3";
- update_ubo_params(sc, u);
+ update_uniform_params(sc, u);
for (int n = 0; n < 9; n++)
u->v.f[n] = v[n];
if (transpose)
transpose3x3(&u->v.f[0]);
}
-// Tell the shader generator (and later gl_sc_draw_data()) about the vertex
-// data layout and attribute names. The entries array is terminated with a {0}
-// entry. The array memory must remain valid indefinitely (for now).
-void gl_sc_set_vertex_format(struct gl_shader_cache *sc,
- const struct ra_renderpass_input *entries,
- int vertex_stride)
-{
- sc->params.vertex_attribs = (struct ra_renderpass_input *)entries;
- sc->params.num_vertex_attribs = 0;
- while (entries[sc->params.num_vertex_attribs].name)
- sc->params.num_vertex_attribs++;
- sc->params.vertex_stride = vertex_stride;
-}
-
void gl_sc_blend(struct gl_shader_cache *sc,
enum ra_blend blend_src_rgb,
enum ra_blend blend_dst_rgb,
@@ -468,6 +484,20 @@ static void update_ubo(struct ra *ra, struct ra_buf *ubo, struct sc_uniform *u)
}
}
+static void update_pushc(struct ra *ra, void *pushc, struct sc_uniform *u)
+{
+ uintptr_t src = (uintptr_t) &u->v;
+ uintptr_t dst = (uintptr_t) pushc + (ptrdiff_t) u->offset;
+ struct ra_layout src_layout = ra_renderpass_input_layout(&u->input);
+ struct ra_layout dst_layout = u->layout;
+
+ for (int i = 0; i < u->input.dim_m; i++) {
+ memcpy((void *)dst, (void *)src, src_layout.stride);
+ src += src_layout.stride;
+ dst += dst_layout.stride;
+ }
+}
+
static void update_uniform(struct gl_shader_cache *sc, struct sc_entry *e,
struct sc_uniform *u, int n)
{
@@ -479,6 +509,13 @@ static void update_uniform(struct gl_shader_cache *sc, struct sc_entry *e,
un->v = u->v;
un->set = true;
+ static const char *desc[] = {
+ [SC_UNIFORM_TYPE_UBO] = "UBO",
+ [SC_UNIFORM_TYPE_PUSHC] = "PC",
+ [SC_UNIFORM_TYPE_GLOBAL] = "global",
+ };
+ MP_TRACE(sc, "Updating %s uniform '%s'\n", desc[u->type], u->input.name);
+
switch (u->type) {
case SC_UNIFORM_TYPE_GLOBAL: {
struct ra_renderpass_input_val value = {
@@ -492,6 +529,10 @@ static void update_uniform(struct gl_shader_cache *sc, struct sc_entry *e,
assert(e->ubo);
update_ubo(sc->ra, e->ubo, u);
break;
+ case SC_UNIFORM_TYPE_PUSHC:
+ assert(e->pushc);
+ update_pushc(sc->ra, e->pushc, u);
+ break;
default: abort();
}
}
@@ -509,25 +550,6 @@ static bool create_pass(struct gl_shader_cache *sc, struct sc_entry *entry)
void *tmp = talloc_new(NULL);
struct ra_renderpass_params params = sc->params;
- MP_VERBOSE(sc, "new shader program:\n");
- if (sc->header_text.len) {
- MP_VERBOSE(sc, "header:\n");
- mp_log_source(sc->log, MSGL_V, sc->header_text.start);
- MP_VERBOSE(sc, "body:\n");
- }
- if (sc->text.len)
- mp_log_source(sc->log, MSGL_V, sc->text.start);
-
- // The vertex shader uses mangled names for the vertex attributes, so that
- // the fragment shader can use the "real" names. But the shader is expecting
- // the vertex attribute names (at least with older GLSL targets for GL).
- params.vertex_attribs = talloc_memdup(tmp, params.vertex_attribs,
- params.num_vertex_attribs * sizeof(params.vertex_attribs[0]));
- for (int n = 0; n < params.num_vertex_attribs; n++) {
- struct ra_renderpass_input *attrib = &params.vertex_attribs[n];
- attrib->name = talloc_asprintf(tmp, "vertex_%s", attrib->name);
- }
-
const char *cache_header = "mpv shader cache v1\n";
char *cache_filename = NULL;
char *cache_dir = NULL;
@@ -552,7 +574,7 @@ static bool create_pass(struct gl_shader_cache *sc, struct sc_entry *entry)
cache_filename = mp_path_join(tmp, cache_dir, hashstr);
if (stat(cache_filename, &(struct stat){0}) == 0) {
- MP_VERBOSE(sc, "Trying to load shader from disk...\n");
+ MP_DBG(sc, "Trying to load shader from disk...\n");
struct bstr cachedata =
stream_read_file(cache_filename, tmp, sc->global, 1000000000);
if (bstr_eatstart0(&cachedata, cache_header))
@@ -574,9 +596,10 @@ static bool create_pass(struct gl_shader_cache *sc, struct sc_entry *entry)
MP_TARRAY_APPEND(sc, params.inputs, params.num_inputs, ubo_input);
}
- entry->pass = sc->ra->fns->renderpass_create(sc->ra, &params);
- if (!entry->pass)
- goto error;
+ if (sc->pushc_size) {
+ params.push_constants_size = MP_ALIGN_UP(sc->pushc_size, 4);
+ entry->pushc = talloc_zero_size(entry, params.push_constants_size);
+ }
if (sc->ubo_size) {
struct ra_buf_params ubo_params = {
@@ -592,12 +615,16 @@ static bool create_pass(struct gl_shader_cache *sc, struct sc_entry *entry)
}
}
+ entry->pass = sc->ra->fns->renderpass_create(sc->ra, &params);
+ if (!entry->pass)
+ goto error;
+
if (entry->pass && cache_filename) {
bstr nc = entry->pass->params.cached_program;
if (nc.len && !bstr_equals(params.cached_program, nc)) {
mp_mkdirp(cache_dir);
- MP_VERBOSE(sc, "Writing shader cache file: %s\n", cache_filename);
+ MP_DBG(sc, "Writing shader cache file: %s\n", cache_filename);
FILE *out = fopen(cache_filename, "wb");
if (out) {
fwrite(cache_header, strlen(cache_header), 1, out);
@@ -626,8 +653,22 @@ static void add_uniforms(struct gl_shader_cache *sc, bstr *dst)
struct sc_uniform *u = &sc->uniforms[n];
if (u->type != SC_UNIFORM_TYPE_UBO)
continue;
- ADD(dst, "layout(offset=%zu) %s %s;\n", u->offset,
- u->glsl_type, u->input.name);
+ ADD(dst, "layout(offset=%zu) %s %s;\n", u->offset, u->glsl_type,
+ u->input.name);
+ }
+ ADD(dst, "};\n");
+ }
+
+ // Ditto for push constants
+ if (sc->pushc_size > 0) {
+ ADD(dst, "layout(std430, push_constant) uniform PushC {\n");
+ for (int n = 0; n < sc->num_uniforms; n++) {
+ struct sc_uniform *u = &sc->uniforms[n];
+ if (u->type != SC_UNIFORM_TYPE_PUSHC)
+ continue;
+ // push constants don't support explicit offsets
+ ADD(dst, "/*offset=%zu*/ %s %s;\n", u->offset, u->glsl_type,
+ u->input.name);
}
ADD(dst, "};\n");
}
@@ -642,7 +683,6 @@ static void add_uniforms(struct gl_shader_cache *sc, bstr *dst)
assert(sc->ra->caps & RA_CAP_GLOBAL_UNIFORM);
// fall through
case RA_VARTYPE_TEX:
- case RA_VARTYPE_IMG_W:
// Vulkan requires explicitly assigning the bindings in the shader
// source. For OpenGL it's optional, but requires higher GL version
// so we don't do it (and instead have ra_gl update the bindings
@@ -659,6 +699,22 @@ static void add_uniforms(struct gl_shader_cache *sc, bstr *dst)
ADD(dst, "layout(std430, binding=%d) buffer %s { %s };\n",
u->input.binding, u->input.name, u->buffer_format);
break;
+ case RA_VARTYPE_IMG_W: {
+ // For better compatibility, we have to explicitly label the
+ // type of data we will be reading/writing to this image.
+ const char *fmt = u->v.tex->params.format->glsl_format;
+
+ if (sc->ra->glsl_vulkan) {
+ if (fmt) {
+ ADD(dst, "layout(binding=%d, %s) ", u->input.binding, fmt);
+ } else {
+ ADD(dst, "layout(binding=%d) ", u->input.binding);
+ }
+ } else if (fmt) {
+ ADD(dst, "layout(%s) ", fmt);
+ }
+ ADD(dst, "uniform %s %s;\n", u->glsl_type, u->input.name);
+ }
}
}
}
@@ -674,7 +730,9 @@ static void add_uniforms(struct gl_shader_cache *sc, bstr *dst)
// and fragment operations needed for the next program have to be re-added.)
static void gl_sc_generate(struct gl_shader_cache *sc,
enum ra_renderpass_type type,
- const struct ra_format *target_format)
+ const struct ra_format *target_format,
+ const struct ra_renderpass_input *vao,
+ int vao_len, size_t vertex_stride)
{
int glsl_version = sc->ra->glsl_version;
int glsl_es = sc->ra->glsl_es ? glsl_version : 0;
@@ -686,9 +744,6 @@ static void gl_sc_generate(struct gl_shader_cache *sc,
assert(!sc->needs_reset);
sc->needs_reset = true;
- // gl_sc_set_vertex_format() must always be called
- assert(sc->params.vertex_attribs);
-
// If using a UBO, pick a binding (needed for shader generation)
if (sc->ubo_size)
sc->ubo_binding = gl_sc_next_binding(sc, RA_VARTYPE_BUF_RO);
@@ -745,8 +800,8 @@ static void gl_sc_generate(struct gl_shader_cache *sc,
bstr *vert_body = &sc->tmp[2];
ADD(vert_body, "void main() {\n");
bstr *frag_vaos = &sc->tmp[3];
- for (int n = 0; n < sc->params.num_vertex_attribs; n++) {
- const struct ra_renderpass_input *e = &sc->params.vertex_attribs[n];
+ for (int n = 0; n < vao_len; n++) {
+ const struct ra_renderpass_input *e = &vao[n];
const char *glsl_type = vao_glsl_type(e);
char loc[32] = {0};
if (sc->ra->glsl_vulkan)
@@ -857,6 +912,19 @@ static void gl_sc_generate(struct gl_shader_cache *sc,
.total = bstrdup(entry, *hash_total),
.timer = timer_pool_create(sc->ra),
};
+
+ // The vertex shader uses mangled names for the vertex attributes, so
+ // that the fragment shader can use the "real" names. But the shader is
+ // expecting the vertex attribute names (at least with older GLSL
+ // targets for GL).
+ sc->params.vertex_stride = vertex_stride;
+ for (int n = 0; n < vao_len; n++) {
+ struct ra_renderpass_input attrib = vao[n];
+ attrib.name = talloc_asprintf(entry, "vertex_%s", attrib.name);
+ MP_TARRAY_APPEND(sc, sc->params.vertex_attribs,
+ sc->params.num_vertex_attribs, attrib);
+ }
+
for (int n = 0; n < sc->num_uniforms; n++) {
struct sc_cached_uniform u = {0};
if (sc->uniforms[n].type == SC_UNIFORM_TYPE_GLOBAL) {
@@ -872,8 +940,11 @@ static void gl_sc_generate(struct gl_shader_cache *sc,
sc->error_state = true;
MP_TARRAY_APPEND(sc, sc->entries, sc->num_entries, entry);
}
- if (sc->error_state)
+
+ if (!entry->pass) {
+ sc->current_shader = NULL;
return;
+ }
assert(sc->num_uniforms == entry->num_cached_uniforms);
@@ -895,11 +966,14 @@ static void gl_sc_generate(struct gl_shader_cache *sc,
struct mp_pass_perf gl_sc_dispatch_draw(struct gl_shader_cache *sc,
struct ra_tex *target,
- void *ptr, size_t num)
+ const struct ra_renderpass_input *vao,
+ int vao_len, size_t vertex_stride,
+ void *vertices, size_t num_vertices)
{
struct timer_pool *timer = NULL;
- gl_sc_generate(sc, RA_RENDERPASS_TYPE_RASTER, target->params.format);
+ gl_sc_generate(sc, RA_RENDERPASS_TYPE_RASTER, target->params.format,
+ vao, vao_len, vertex_stride);
if (!sc->current_shader)
goto error;
@@ -911,9 +985,10 @@ struct mp_pass_perf gl_sc_dispatch_draw(struct gl_shader_cache *sc,
.pass = sc->current_shader->pass,
.values = sc->values,
.num_values = sc->num_values,
+ .push_constants = sc->current_shader->pushc,
.target = target,
- .vertex_data = ptr,
- .vertex_count = num,
+ .vertex_data = vertices,
+ .vertex_count = num_vertices,
.viewport = full_rc,
.scissors = full_rc,
};
@@ -932,7 +1007,7 @@ struct mp_pass_perf gl_sc_dispatch_compute(struct gl_shader_cache *sc,
{
struct timer_pool *timer = NULL;
- gl_sc_generate(sc, RA_RENDERPASS_TYPE_COMPUTE, NULL);
+ gl_sc_generate(sc, RA_RENDERPASS_TYPE_COMPUTE, NULL, NULL, 0, 0);
if (!sc->current_shader)
goto error;
@@ -942,6 +1017,7 @@ struct mp_pass_perf gl_sc_dispatch_compute(struct gl_shader_cache *sc,
.pass = sc->current_shader->pass,
.values = sc->values,
.num_values = sc->num_values,
+ .push_constants = sc->current_shader->pushc,
.compute_groups = {w, h, d},
};
diff --git a/video/out/opengl/shader_cache.h b/video/out/gpu/shader_cache.h
index 82a0780..2fe7dcf 100644
--- a/video/out/opengl/shader_cache.h
+++ b/video/out/gpu/shader_cache.h
@@ -25,6 +25,10 @@ void gl_sc_haddf(struct gl_shader_cache *sc, const char *textf, ...)
void gl_sc_hadd_bstr(struct gl_shader_cache *sc, struct bstr text);
void gl_sc_paddf(struct gl_shader_cache *sc, const char *textf, ...)
PRINTF_ATTRIBUTE(2, 3);
+
+// A hint that the next data-type (i.e. non-binding) uniform is expected to
+// change frequently. This refers to the _f, _i, _vecN etc. uniform types.
+void gl_sc_uniform_dynamic(struct gl_shader_cache *sc);
void gl_sc_uniform_texture(struct gl_shader_cache *sc, char *name,
struct ra_tex *tex);
void gl_sc_uniform_image2D_wo(struct gl_shader_cache *sc, const char *name,
@@ -39,9 +43,6 @@ void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
bool transpose, float *v);
void gl_sc_uniform_mat3(struct gl_shader_cache *sc, char *name,
bool transpose, float *v);
-void gl_sc_set_vertex_format(struct gl_shader_cache *sc,
- const struct ra_renderpass_input *vertex_attribs,
- int vertex_stride);
void gl_sc_blend(struct gl_shader_cache *sc,
enum ra_blend blend_src_rgb,
enum ra_blend blend_dst_rgb,
@@ -50,7 +51,12 @@ void gl_sc_blend(struct gl_shader_cache *sc,
void gl_sc_enable_extension(struct gl_shader_cache *sc, char *name);
struct mp_pass_perf gl_sc_dispatch_draw(struct gl_shader_cache *sc,
struct ra_tex *target,
+ const struct ra_renderpass_input *vao,
+ int vao_len, size_t vertex_stride,
void *ptr, size_t num);
struct mp_pass_perf gl_sc_dispatch_compute(struct gl_shader_cache *sc,
int w, int h, int d);
+// The application can call this on errors, to reset the current shader. This
+// is normally done implicitly by gl_sc_dispatch_*
+void gl_sc_reset(struct gl_shader_cache *sc);
void gl_sc_set_cache_dir(struct gl_shader_cache *sc, const char *dir);
diff --git a/video/out/gpu/spirv.c b/video/out/gpu/spirv.c
new file mode 100644
index 0000000..e20fbe7
--- /dev/null
+++ b/video/out/gpu/spirv.c
@@ -0,0 +1,78 @@
+#include "common/msg.h"
+#include "options/m_config.h"
+
+#include "spirv.h"
+#include "config.h"
+
+extern const struct spirv_compiler_fns spirv_shaderc;
+extern const struct spirv_compiler_fns spirv_nvidia_builtin;
+
+// in probe-order
+enum {
+ SPIRV_AUTO = 0,
+ SPIRV_SHADERC, // generally preferred, but not packaged everywhere
+ SPIRV_NVIDIA, // can be useful for testing, only available on nvidia
+};
+
+static const struct spirv_compiler_fns *compilers[] = {
+#if HAVE_SHADERC
+ [SPIRV_SHADERC] = &spirv_shaderc,
+#endif
+#if HAVE_VULKAN
+ [SPIRV_NVIDIA] = &spirv_nvidia_builtin,
+#endif
+};
+
+static const struct m_opt_choice_alternatives compiler_choices[] = {
+ {"auto", SPIRV_AUTO},
+#if HAVE_SHADERC
+ {"shaderc", SPIRV_SHADERC},
+#endif
+#if HAVE_VULKAN
+ {"nvidia", SPIRV_NVIDIA},
+#endif
+ {0}
+};
+
+struct spirv_opts {
+ int compiler;
+};
+
+#define OPT_BASE_STRUCT struct spirv_opts
+const struct m_sub_options spirv_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_CHOICE_C("spirv-compiler", compiler, 0, compiler_choices),
+ {0}
+ },
+ .size = sizeof(struct spirv_opts),
+};
+
+bool spirv_compiler_init(struct ra_ctx *ctx)
+{
+ void *tmp = talloc_new(NULL);
+ struct spirv_opts *opts = mp_get_config_group(tmp, ctx->global, &spirv_conf);
+ int compiler = opts->compiler;
+ talloc_free(tmp);
+
+ for (int i = SPIRV_AUTO+1; i < MP_ARRAY_SIZE(compilers); i++) {
+ if (compiler != SPIRV_AUTO && i != compiler)
+ continue;
+ if (!compilers[i])
+ continue;
+
+ ctx->spirv = talloc_zero(ctx, struct spirv_compiler);
+ ctx->spirv->log = ctx->log,
+ ctx->spirv->fns = compilers[i];
+
+ const char *name = m_opt_choice_str(compiler_choices, i);
+ strncpy(ctx->spirv->name, name, sizeof(ctx->spirv->name));
+ MP_VERBOSE(ctx, "Initializing SPIR-V compiler '%s'\n", name);
+ if (ctx->spirv->fns->init(ctx))
+ return true;
+ talloc_free(ctx->spirv);
+ ctx->spirv = NULL;
+ }
+
+ MP_ERR(ctx, "Failed initializing SPIR-V compiler!\n");
+ return false;
+}
diff --git a/video/out/gpu/spirv.h b/video/out/gpu/spirv.h
new file mode 100644
index 0000000..e3dbd4f
--- /dev/null
+++ b/video/out/gpu/spirv.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "common/msg.h"
+#include "common/common.h"
+#include "context.h"
+
+enum glsl_shader {
+ GLSL_SHADER_VERTEX,
+ GLSL_SHADER_FRAGMENT,
+ GLSL_SHADER_COMPUTE,
+};
+
+#define SPIRV_NAME_MAX_LEN 32
+
+struct spirv_compiler {
+ char name[SPIRV_NAME_MAX_LEN];
+ const struct spirv_compiler_fns *fns;
+ struct mp_log *log;
+ void *priv;
+
+ const char *required_ext; // or NULL
+ int glsl_version; // GLSL version supported
+ int compiler_version; // for cache invalidation, may be left as 0
+ int ra_caps; // RA_CAP_* provided by this implementation, if any
+};
+
+struct spirv_compiler_fns {
+ // Compile GLSL to SPIR-V, under GL_KHR_vulkan_glsl semantics.
+ bool (*compile_glsl)(struct spirv_compiler *spirv, void *tactx,
+ enum glsl_shader type, const char *glsl,
+ struct bstr *out_spirv);
+
+ // Called by spirv_compiler_init / ra_ctx_destroy. These don't need to
+ // allocate/free ctx->spirv, that is done by the caller
+ bool (*init)(struct ra_ctx *ctx);
+ void (*uninit)(struct ra_ctx *ctx); // optional
+};
+
+// Initializes ctx->spirv to a valid SPIR-V compiler, or returns false on
+// failure. Cleanup will be handled by ra_ctx_destroy.
+bool spirv_compiler_init(struct ra_ctx *ctx);
diff --git a/video/out/gpu/spirv_shaderc.c b/video/out/gpu/spirv_shaderc.c
new file mode 100644
index 0000000..ee70205
--- /dev/null
+++ b/video/out/gpu/spirv_shaderc.c
@@ -0,0 +1,125 @@
+#include "common/msg.h"
+
+#include "context.h"
+#include "spirv.h"
+
+#include <shaderc/shaderc.h>
+
+struct priv {
+ shaderc_compiler_t compiler;
+ shaderc_compile_options_t opts;
+};
+
+static void shaderc_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->spirv->priv;
+ if (!p)
+ return;
+
+ shaderc_compile_options_release(p->opts);
+ shaderc_compiler_release(p->compiler);
+}
+
+static bool shaderc_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->spirv->priv = talloc_zero(ctx->spirv, struct priv);
+
+ p->compiler = shaderc_compiler_initialize();
+ if (!p->compiler)
+ goto error;
+ p->opts = shaderc_compile_options_initialize();
+ if (!p->opts)
+ goto error;
+
+ shaderc_compile_options_set_optimization_level(p->opts,
+ shaderc_optimization_level_size);
+ if (ctx->opts.debug)
+ shaderc_compile_options_set_generate_debug_info(p->opts);
+
+ int ver, rev;
+ shaderc_get_spv_version(&ver, &rev);
+ ctx->spirv->compiler_version = ver * 100 + rev; // forwards compatibility
+ ctx->spirv->glsl_version = 450; // impossible to query?
+ return true;
+
+error:
+ shaderc_uninit(ctx);
+ return false;
+}
+
+static shaderc_compilation_result_t compile(struct priv *p,
+ enum glsl_shader type,
+ const char *glsl, bool debug)
+{
+ static const shaderc_shader_kind kinds[] = {
+ [GLSL_SHADER_VERTEX] = shaderc_glsl_vertex_shader,
+ [GLSL_SHADER_FRAGMENT] = shaderc_glsl_fragment_shader,
+ [GLSL_SHADER_COMPUTE] = shaderc_glsl_compute_shader,
+ };
+
+ if (debug) {
+ return shaderc_compile_into_spv_assembly(p->compiler, glsl, strlen(glsl),
+ kinds[type], "input", "main", p->opts);
+ } else {
+ return shaderc_compile_into_spv(p->compiler, glsl, strlen(glsl),
+ kinds[type], "input", "main", p->opts);
+ }
+}
+
+static bool shaderc_compile(struct spirv_compiler *spirv, void *tactx,
+ enum glsl_shader type, const char *glsl,
+ struct bstr *out_spirv)
+{
+ struct priv *p = spirv->priv;
+
+ shaderc_compilation_result_t res = compile(p, type, glsl, false);
+ int errs = shaderc_result_get_num_errors(res),
+ warn = shaderc_result_get_num_warnings(res),
+ msgl = errs ? MSGL_ERR : warn ? MSGL_WARN : MSGL_V;
+
+ const char *msg = shaderc_result_get_error_message(res);
+ if (msg[0])
+ MP_MSG(spirv, msgl, "shaderc output:\n%s", msg);
+
+ int s = shaderc_result_get_compilation_status(res);
+ bool success = s == shaderc_compilation_status_success;
+
+ static const char *results[] = {
+ [shaderc_compilation_status_success] = "success",
+ [shaderc_compilation_status_invalid_stage] = "invalid stage",
+ [shaderc_compilation_status_compilation_error] = "error",
+ [shaderc_compilation_status_internal_error] = "internal error",
+ [shaderc_compilation_status_null_result_object] = "no result",
+ [shaderc_compilation_status_invalid_assembly] = "invalid assembly",
+ };
+
+ const char *status = s < MP_ARRAY_SIZE(results) ? results[s] : "unknown";
+ MP_MSG(spirv, msgl, "shaderc compile status '%s' (%d errors, %d warnings)\n",
+ status, errs, warn);
+
+ if (success) {
+ void *bytes = (void *) shaderc_result_get_bytes(res);
+ out_spirv->len = shaderc_result_get_length(res);
+ out_spirv->start = talloc_memdup(tactx, bytes, out_spirv->len);
+ }
+
+ // Also print SPIR-V disassembly for debugging purposes. Unfortunately
+ // there doesn't seem to be a way to get this except compiling the shader
+ // a second time..
+ if (mp_msg_test(spirv->log, MSGL_TRACE)) {
+ shaderc_compilation_result_t dis = compile(p, type, glsl, true);
+ MP_TRACE(spirv, "Generated SPIR-V:\n%.*s",
+ (int)shaderc_result_get_length(dis),
+ shaderc_result_get_bytes(dis));
+ shaderc_result_release(dis);
+ }
+
+ shaderc_result_release(res);
+ return success;
+}
+
+const struct spirv_compiler_fns spirv_shaderc = {
+ .compile_glsl = shaderc_compile,
+ .init = shaderc_init,
+ .uninit = shaderc_uninit,
+};
diff --git a/video/out/opengl/user_shaders.c b/video/out/gpu/user_shaders.c
index 58a1ac9..446941b 100644
--- a/video/out/opengl/user_shaders.c
+++ b/video/out/gpu/user_shaders.c
@@ -17,9 +17,9 @@
#include <assert.h>
+#include "common/msg.h"
#include "misc/ctype.h"
#include "user_shaders.h"
-#include "formats.h"
static bool parse_rpn_szexpr(struct bstr line, struct szexp out[MAX_SZEXP_SIZE])
{
diff --git a/video/out/opengl/user_shaders.h b/video/out/gpu/user_shaders.h
index 94a070c..8d8cc6b 100644
--- a/video/out/opengl/user_shaders.h
+++ b/video/out/gpu/user_shaders.h
@@ -21,10 +21,8 @@
#include "utils.h"
#include "ra.h"
-#define SHADER_MAX_PASSES 32
#define SHADER_MAX_HOOKS 16
-#define SHADER_MAX_BINDS 6
-#define SHADER_MAX_SAVED 64
+#define SHADER_MAX_BINDS 16
#define MAX_SZEXP_SIZE 32
enum szexp_op {
diff --git a/video/out/gpu/utils.c b/video/out/gpu/utils.c
new file mode 100644
index 0000000..078a31c
--- /dev/null
+++ b/video/out/gpu/utils.c
@@ -0,0 +1,332 @@
+#include "common/msg.h"
+#include "video/out/vo.h"
+#include "utils.h"
+
+// Standard parallel 2D projection, except y1 < y0 means that the coordinate
+// system is flipped, not the projection.
+void gl_transform_ortho(struct gl_transform *t, float x0, float x1,
+ float y0, float y1)
+{
+ if (y1 < y0) {
+ float tmp = y0;
+ y0 = tmp - y1;
+ y1 = tmp;
+ }
+
+ t->m[0][0] = 2.0f / (x1 - x0);
+ t->m[0][1] = 0.0f;
+ t->m[1][0] = 0.0f;
+ t->m[1][1] = 2.0f / (y1 - y0);
+ t->t[0] = -(x1 + x0) / (x1 - x0);
+ t->t[1] = -(y1 + y0) / (y1 - y0);
+}
+
+// Apply the effects of one transformation to another, transforming it in the
+// process. In other words: post-composes t onto x
+void gl_transform_trans(struct gl_transform t, struct gl_transform *x)
+{
+ struct gl_transform xt = *x;
+ x->m[0][0] = t.m[0][0] * xt.m[0][0] + t.m[0][1] * xt.m[1][0];
+ x->m[1][0] = t.m[1][0] * xt.m[0][0] + t.m[1][1] * xt.m[1][0];
+ x->m[0][1] = t.m[0][0] * xt.m[0][1] + t.m[0][1] * xt.m[1][1];
+ x->m[1][1] = t.m[1][0] * xt.m[0][1] + t.m[1][1] * xt.m[1][1];
+ gl_transform_vec(t, &x->t[0], &x->t[1]);
+}
+
+void gl_transform_ortho_fbo(struct gl_transform *t, struct ra_fbo fbo)
+{
+ int y_dir = fbo.flip ? -1 : 1;
+ gl_transform_ortho(t, 0, fbo.tex->params.w, 0, fbo.tex->params.h * y_dir);
+}
+
+void ra_buf_pool_uninit(struct ra *ra, struct ra_buf_pool *pool)
+{
+ for (int i = 0; i < pool->num_buffers; i++)
+ ra_buf_free(ra, &pool->buffers[i]);
+
+ talloc_free(pool->buffers);
+ *pool = (struct ra_buf_pool){0};
+}
+
+static bool ra_buf_params_compatible(const struct ra_buf_params *new,
+ const struct ra_buf_params *old)
+{
+ return new->type == old->type &&
+ new->size <= old->size &&
+ new->host_mapped == old->host_mapped &&
+ new->host_mutable == old->host_mutable;
+}
+
+static bool ra_buf_pool_grow(struct ra *ra, struct ra_buf_pool *pool)
+{
+ struct ra_buf *buf = ra_buf_create(ra, &pool->current_params);
+ if (!buf)
+ return false;
+
+ MP_TARRAY_INSERT_AT(NULL, pool->buffers, pool->num_buffers, pool->index, buf);
+ MP_VERBOSE(ra, "Resized buffer pool of type %u to size %d\n",
+ pool->current_params.type, pool->num_buffers);
+ return true;
+}
+
+struct ra_buf *ra_buf_pool_get(struct ra *ra, struct ra_buf_pool *pool,
+ const struct ra_buf_params *params)
+{
+ assert(!params->initial_data);
+
+ if (!ra_buf_params_compatible(params, &pool->current_params)) {
+ ra_buf_pool_uninit(ra, pool);
+ pool->current_params = *params;
+ }
+
+ // Make sure we have at least one buffer available
+ if (!pool->buffers && !ra_buf_pool_grow(ra, pool))
+ return NULL;
+
+ // Make sure the next buffer is available for use
+ if (!ra->fns->buf_poll(ra, pool->buffers[pool->index]) &&
+ !ra_buf_pool_grow(ra, pool))
+ {
+ return NULL;
+ }
+
+ struct ra_buf *buf = pool->buffers[pool->index++];
+ pool->index %= pool->num_buffers;
+
+ return buf;
+}
+
+bool ra_tex_upload_pbo(struct ra *ra, struct ra_buf_pool *pbo,
+ const struct ra_tex_upload_params *params)
+{
+ if (params->buf)
+ return ra->fns->tex_upload(ra, params);
+
+ struct ra_tex *tex = params->tex;
+ size_t row_size = tex->params.dimensions == 2 ? params->stride :
+ tex->params.w * tex->params.format->pixel_size;
+
+ int height = tex->params.h;
+ if (tex->params.dimensions == 2 && params->rc)
+ height = mp_rect_h(*params->rc);
+
+ struct ra_buf_params bufparams = {
+ .type = RA_BUF_TYPE_TEX_UPLOAD,
+ .size = row_size * height * tex->params.d,
+ .host_mutable = true,
+ };
+
+ struct ra_buf *buf = ra_buf_pool_get(ra, pbo, &bufparams);
+ if (!buf)
+ return false;
+
+ ra->fns->buf_update(ra, buf, 0, params->src, bufparams.size);
+
+ struct ra_tex_upload_params newparams = *params;
+ newparams.buf = buf;
+ newparams.src = NULL;
+
+ return ra->fns->tex_upload(ra, &newparams);
+}
+
+struct ra_layout std140_layout(struct ra_renderpass_input *inp)
+{
+ size_t el_size = ra_vartype_size(inp->type);
+
+ // std140 packing rules:
+ // 1. The alignment of generic values is their size in bytes
+ // 2. The alignment of vectors is the vector length * the base count, with
+ // the exception of vec3 which is always aligned like vec4
+ // 3. The alignment of arrays is that of the element size rounded up to
+ // the nearest multiple of vec4
+ // 4. Matrices are treated like arrays of vectors
+ // 5. Arrays/matrices are laid out with a stride equal to the alignment
+ size_t size = el_size * inp->dim_v;
+ if (inp->dim_v == 3)
+ size += el_size;
+ if (inp->dim_m > 1)
+ size = MP_ALIGN_UP(size, sizeof(float[4]));
+
+ return (struct ra_layout) {
+ .align = size,
+ .stride = size,
+ .size = size * inp->dim_m,
+ };
+}
+
+struct ra_layout std430_layout(struct ra_renderpass_input *inp)
+{
+ size_t el_size = ra_vartype_size(inp->type);
+
+ // std430 packing rules: like std140, except arrays/matrices are always
+ // "tightly" packed, even arrays/matrices of vec3s
+ size_t size = el_size * inp->dim_v;
+ if (inp->dim_v == 3 && inp->dim_m == 1)
+ size += el_size;
+
+ return (struct ra_layout) {
+ .align = size,
+ .stride = size,
+ .size = size * inp->dim_m,
+ };
+}
+
+// Resize a texture to a new desired size and format if necessary
+bool ra_tex_resize(struct ra *ra, struct mp_log *log, struct ra_tex **tex,
+ int w, int h, const struct ra_format *fmt)
+{
+ if (*tex) {
+ struct ra_tex_params cur_params = (*tex)->params;
+ if (cur_params.w == w && cur_params.h == h && cur_params.format == fmt)
+ return true;
+ }
+
+ mp_dbg(log, "Resizing texture: %dx%d\n", w, h);
+
+ if (!fmt || !fmt->renderable || !fmt->linear_filter) {
+ mp_err(log, "Format %s not supported.\n", fmt ? fmt->name : "(unset)");
+ return false;
+ }
+
+ ra_tex_free(ra, tex);
+ struct ra_tex_params params = {
+ .dimensions = 2,
+ .w = w,
+ .h = h,
+ .d = 1,
+ .format = fmt,
+ .src_linear = true,
+ .render_src = true,
+ .render_dst = true,
+ .storage_dst = true,
+ .blit_src = true,
+ };
+
+ *tex = ra_tex_create(ra, &params);
+ if (!*tex)
+ mp_err(log, "Error: texture could not be created.\n");
+
+ return *tex;
+}
+
+struct timer_pool {
+ struct ra *ra;
+ ra_timer *timer;
+ bool running; // detect invalid usage
+
+ uint64_t samples[VO_PERF_SAMPLE_COUNT];
+ int sample_idx;
+ int sample_count;
+
+ uint64_t sum;
+ uint64_t peak;
+};
+
+struct timer_pool *timer_pool_create(struct ra *ra)
+{
+ if (!ra->fns->timer_create)
+ return NULL;
+
+ ra_timer *timer = ra->fns->timer_create(ra);
+ if (!timer)
+ return NULL;
+
+ struct timer_pool *pool = talloc(NULL, struct timer_pool);
+ if (!pool) {
+ ra->fns->timer_destroy(ra, timer);
+ return NULL;
+ }
+
+ *pool = (struct timer_pool){ .ra = ra, .timer = timer };
+ return pool;
+}
+
+void timer_pool_destroy(struct timer_pool *pool)
+{
+ if (!pool)
+ return;
+
+ pool->ra->fns->timer_destroy(pool->ra, pool->timer);
+ talloc_free(pool);
+}
+
+void timer_pool_start(struct timer_pool *pool)
+{
+ if (!pool)
+ return;
+
+ assert(!pool->running);
+ pool->ra->fns->timer_start(pool->ra, pool->timer);
+ pool->running = true;
+}
+
+void timer_pool_stop(struct timer_pool *pool)
+{
+ if (!pool)
+ return;
+
+ assert(pool->running);
+ uint64_t res = pool->ra->fns->timer_stop(pool->ra, pool->timer);
+ pool->running = false;
+
+ if (res) {
+ // Input res into the buffer and grab the previous value
+ uint64_t old = pool->samples[pool->sample_idx];
+ pool->sample_count = MPMIN(pool->sample_count + 1, VO_PERF_SAMPLE_COUNT);
+ pool->samples[pool->sample_idx++] = res;
+ pool->sample_idx %= VO_PERF_SAMPLE_COUNT;
+ pool->sum = pool->sum + res - old;
+
+ // Update peak if necessary
+ if (res >= pool->peak) {
+ pool->peak = res;
+ } else if (pool->peak == old) {
+ // It's possible that the last peak was the value we just removed,
+ // if so we need to scan for the new peak
+ uint64_t peak = res;
+ for (int i = 0; i < VO_PERF_SAMPLE_COUNT; i++)
+ peak = MPMAX(peak, pool->samples[i]);
+ pool->peak = peak;
+ }
+ }
+}
+
+struct mp_pass_perf timer_pool_measure(struct timer_pool *pool)
+{
+ if (!pool)
+ return (struct mp_pass_perf){0};
+
+ struct mp_pass_perf res = {
+ .peak = pool->peak,
+ .count = pool->sample_count,
+ };
+
+ int idx = pool->sample_idx - pool->sample_count + VO_PERF_SAMPLE_COUNT;
+ for (int i = 0; i < res.count; i++) {
+ idx %= VO_PERF_SAMPLE_COUNT;
+ res.samples[i] = pool->samples[idx++];
+ }
+
+ if (res.count > 0) {
+ res.last = res.samples[res.count - 1];
+ res.avg = pool->sum / res.count;
+ }
+
+ return res;
+}
+
+void mp_log_source(struct mp_log *log, int lev, const char *src)
+{
+ int line = 1;
+ if (!src)
+ return;
+ while (*src) {
+ const char *end = strchr(src, '\n');
+ const char *next = end + 1;
+ if (!end)
+ next = end = src + strlen(src);
+ mp_msg(log, lev, "[%3d] %.*s\n", line, (int)(end - src), src);
+ line++;
+ src = next;
+ }
+}
diff --git a/video/out/gpu/utils.h b/video/out/gpu/utils.h
new file mode 100644
index 0000000..ac0cbf2
--- /dev/null
+++ b/video/out/gpu/utils.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <stdbool.h>
+#include <math.h>
+
+#include "ra.h"
+#include "context.h"
+
+// A 3x2 matrix, with the translation part separate.
+struct gl_transform {
+ // row-major, e.g. in mathematical notation:
+ // | m[0][0] m[0][1] |
+ // | m[1][0] m[1][1] |
+ float m[2][2];
+ float t[2];
+};
+
+static const struct gl_transform identity_trans = {
+ .m = {{1.0, 0.0}, {0.0, 1.0}},
+ .t = {0.0, 0.0},
+};
+
+void gl_transform_ortho(struct gl_transform *t, float x0, float x1,
+ float y0, float y1);
+
+// This treats m as an affine transformation, in other words m[2][n] gets
+// added to the output.
+static inline void gl_transform_vec(struct gl_transform t, float *x, float *y)
+{
+ float vx = *x, vy = *y;
+ *x = vx * t.m[0][0] + vy * t.m[0][1] + t.t[0];
+ *y = vx * t.m[1][0] + vy * t.m[1][1] + t.t[1];
+}
+
+struct mp_rect_f {
+ float x0, y0, x1, y1;
+};
+
+// Semantic equality (fuzzy comparison)
+static inline bool mp_rect_f_seq(struct mp_rect_f a, struct mp_rect_f b)
+{
+ return fabs(a.x0 - b.x0) < 1e-6 && fabs(a.x1 - b.x1) < 1e-6 &&
+ fabs(a.y0 - b.y0) < 1e-6 && fabs(a.y1 - b.y1) < 1e-6;
+}
+
+static inline void gl_transform_rect(struct gl_transform t, struct mp_rect_f *r)
+{
+ gl_transform_vec(t, &r->x0, &r->y0);
+ gl_transform_vec(t, &r->x1, &r->y1);
+}
+
+static inline bool gl_transform_eq(struct gl_transform a, struct gl_transform b)
+{
+ for (int x = 0; x < 2; x++) {
+ for (int y = 0; y < 2; y++) {
+ if (a.m[x][y] != b.m[x][y])
+ return false;
+ }
+ }
+
+ return a.t[0] == b.t[0] && a.t[1] == b.t[1];
+}
+
+void gl_transform_trans(struct gl_transform t, struct gl_transform *x);
+
+void gl_transform_ortho_fbo(struct gl_transform *t, struct ra_fbo fbo);
+
+// A pool of buffers, which can grow as needed
+struct ra_buf_pool {
+ struct ra_buf_params current_params;
+ struct ra_buf **buffers;
+ int num_buffers;
+ int index;
+};
+
+void ra_buf_pool_uninit(struct ra *ra, struct ra_buf_pool *pool);
+
+// Note: params->initial_data is *not* supported
+struct ra_buf *ra_buf_pool_get(struct ra *ra, struct ra_buf_pool *pool,
+ const struct ra_buf_params *params);
+
+// Helper that wraps ra_tex_upload using texture upload buffers to ensure that
+// params->buf is always set. This is intended for RA-internal usage.
+bool ra_tex_upload_pbo(struct ra *ra, struct ra_buf_pool *pbo,
+ const struct ra_tex_upload_params *params);
+
+// Layout rules for GLSL's packing modes
+struct ra_layout std140_layout(struct ra_renderpass_input *inp);
+struct ra_layout std430_layout(struct ra_renderpass_input *inp);
+
+bool ra_tex_resize(struct ra *ra, struct mp_log *log, struct ra_tex **tex,
+ int w, int h, const struct ra_format *fmt);
+
+// A wrapper around ra_timer that does result pooling, averaging etc.
+struct timer_pool;
+
+struct timer_pool *timer_pool_create(struct ra *ra);
+void timer_pool_destroy(struct timer_pool *pool);
+void timer_pool_start(struct timer_pool *pool);
+void timer_pool_stop(struct timer_pool *pool);
+struct mp_pass_perf timer_pool_measure(struct timer_pool *pool);
+
+// print a multi line string with line numbers (e.g. for shader sources)
+// log, lev: module and log level, as in mp_msg()
+void mp_log_source(struct mp_log *log, int lev, const char *src);
diff --git a/video/out/opengl/video.c b/video/out/gpu/video.c
index 3362381..f80d63a 100644
--- a/video/out/opengl/video.c
+++ b/video/out/gpu/video.c
@@ -60,28 +60,12 @@ static const char *const fixed_tscale_filters[] = {
// must be sorted, and terminated with 0
int filter_sizes[] =
{2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 0};
-int tscale_sizes[] = {2, 4, 6, 0}; // limited by TEXUNIT_VIDEO_NUM
+int tscale_sizes[] = {2, 4, 6, 8, 0};
struct vertex_pt {
float x, y;
};
-struct vertex {
- struct vertex_pt position;
- struct vertex_pt texcoord[TEXUNIT_VIDEO_NUM];
-};
-
-static const struct ra_renderpass_input vertex_vao[] = {
- {"position", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, position)},
- {"texcoord0", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord[0])},
- {"texcoord1", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord[1])},
- {"texcoord2", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord[2])},
- {"texcoord3", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord[3])},
- {"texcoord4", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord[4])},
- {"texcoord5", RA_VARTYPE_FLOAT, 2, 1, offsetof(struct vertex, texcoord[5])},
- {0}
-};
-
struct texplane {
struct ra_tex *tex;
int w, h;
@@ -115,7 +99,7 @@ static const char *plane_names[] = {
// A self-contained description of a source image which can be bound to a
// texture unit and sampled from. Contains metadata about how it's to be used
-struct img_tex {
+struct image {
enum plane_type type; // must be set to something non-zero
int components; // number of relevant coordinates
float multiplier; // multiplier to be used when sampling
@@ -124,10 +108,10 @@ struct img_tex {
struct gl_transform transform; // rendering transformation
};
-// A named img_tex, for user scripting purposes
-struct saved_tex {
+// A named image, for user scripting purposes
+struct saved_img {
const char *name;
- struct img_tex tex;
+ struct image img;
};
// A texture hook. This is some operation that transforms a named texture as
@@ -135,21 +119,21 @@ struct saved_tex {
struct tex_hook {
const char *save_tex;
const char *hook_tex[SHADER_MAX_HOOKS];
- const char *bind_tex[TEXUNIT_VIDEO_NUM];
+ const char *bind_tex[SHADER_MAX_BINDS];
int components; // how many components are relevant (0 = same as input)
void *priv; // this gets talloc_freed when the tex_hook is removed
- void (*hook)(struct gl_video *p, struct img_tex tex, // generates GLSL
+ void (*hook)(struct gl_video *p, struct image img, // generates GLSL
struct gl_transform *trans, void *priv);
- bool (*cond)(struct gl_video *p, struct img_tex tex, void *priv);
+ bool (*cond)(struct gl_video *p, struct image img, void *priv);
};
-struct fbosurface {
- struct fbotex fbotex;
+struct surface {
+ struct ra_tex *tex;
uint64_t id;
double pts;
};
-#define FBOSURFACES_MAX 10
+#define SURFACES_MAX 10
struct cached_file {
char *path;
@@ -161,8 +145,6 @@ struct pass_info {
struct mp_pass_perf perf;
};
-#define PASS_INFO_MAX (SHADER_MAX_PASSES + 32)
-
struct dr_buffer {
struct ra_buf *buf;
// The mpi reference will keep the data from being recycled (or from other
@@ -215,29 +197,40 @@ struct gl_video {
bool dumb_mode;
bool forced_dumb_mode;
+ // Cached vertex array, to avoid re-allocation per frame. For simplicity,
+ // our vertex format is simply a list of `vertex_pt`s, since this greatly
+ // simplifies offset calculation at the cost of (unneeded) flexibility.
+ struct vertex_pt *tmp_vertex;
+ struct ra_renderpass_input *vao;
+ int vao_len;
+
const struct ra_format *fbo_format;
- struct fbotex merge_fbo[4];
- struct fbotex scale_fbo[4];
- struct fbotex integer_fbo[4];
- struct fbotex indirect_fbo;
- struct fbotex blend_subs_fbo;
- struct fbotex screen_fbo;
- struct fbotex output_fbo;
- struct fbosurface surfaces[FBOSURFACES_MAX];
- struct fbotex vdpau_deinterleave_fbo[2];
+ struct ra_tex *merge_tex[4];
+ struct ra_tex *scale_tex[4];
+ struct ra_tex *integer_tex[4];
+ struct ra_tex *indirect_tex;
+ struct ra_tex *blend_subs_tex;
+ struct ra_tex *screen_tex;
+ struct ra_tex *output_tex;
+ struct ra_tex *vdpau_deinterleave_tex[2];
+ struct ra_tex **hook_textures;
+ int num_hook_textures;
+ int idx_hook_textures;
+
struct ra_buf *hdr_peak_ssbo;
+ struct surface surfaces[SURFACES_MAX];
// user pass descriptions and textures
- struct tex_hook tex_hooks[SHADER_MAX_PASSES];
- int tex_hook_num;
- struct gl_user_shader_tex user_textures[SHADER_MAX_PASSES];
- int user_tex_num;
+ struct tex_hook *tex_hooks;
+ int num_tex_hooks;
+ struct gl_user_shader_tex *user_textures;
+ int num_user_textures;
int surface_idx;
int surface_now;
int frames_drawn;
bool is_interpolated;
- bool output_fbo_valid;
+ bool output_tex_valid;
// state for configured scalers
struct scaler scaler[SCALER_COUNT];
@@ -249,9 +242,15 @@ struct gl_video {
struct mp_osd_res osd_rect; // OSD size/margins
// temporary during rendering
- struct img_tex pass_tex[TEXUNIT_VIDEO_NUM];
struct compute_info pass_compute; // compute shader metadata for this pass
- int pass_tex_num;
+ struct image *pass_imgs; // bound images for this pass
+ int num_pass_imgs;
+ struct saved_img *saved_imgs; // saved (named) images for this frame
+ int num_saved_imgs;
+
+ // effective current texture metadata - this will essentially affect the
+ // next render pass target, as well as implicitly tracking what needs to
+ // be done with the image
int texture_w, texture_h;
struct gl_transform texture_offset; // texture transform without rotation
int components;
@@ -259,20 +258,14 @@ struct gl_video {
float user_gamma;
// pass info / metrics
- struct pass_info pass_fresh[PASS_INFO_MAX];
- struct pass_info pass_redraw[PASS_INFO_MAX];
+ struct pass_info pass_fresh[VO_PASS_PERF_MAX];
+ struct pass_info pass_redraw[VO_PASS_PERF_MAX];
struct pass_info *pass;
int pass_idx;
struct timer_pool *upload_timer;
struct timer_pool *blit_timer;
struct timer_pool *osd_timer;
- // intermediate textures
- struct saved_tex saved_tex[SHADER_MAX_SAVED];
- int saved_tex_num;
- struct fbotex hook_fbos[SHADER_MAX_SAVED];
- int hook_fbo_num;
-
int frames_uploaded;
int frames_rendered;
AVLFG lfg;
@@ -284,8 +277,12 @@ struct gl_video {
struct cached_file *files;
int num_files;
- struct ra_hwdec *hwdec;
+ bool hwdec_interop_loading_done;
+ struct ra_hwdec **hwdecs;
+ int num_hwdecs;
+
struct ra_hwdec_mapper *hwdec_mapper;
+ struct ra_hwdec *hwdec_overlay;
bool hwdec_active;
bool dsi_warned;
@@ -318,8 +315,9 @@ static const struct gl_video_opts gl_video_opts_def = {
.gamma = 1.0f,
.tone_mapping = TONE_MAPPING_MOBIUS,
.tone_mapping_param = NAN,
- .tone_mapping_desat = 2.0,
+ .tone_mapping_desat = 1.0,
.early_flush = -1,
+ .hwdec_interop = "auto",
};
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
@@ -347,9 +345,9 @@ static int validate_window_opt(struct mp_log *log, const m_option_t *opt,
const struct m_sub_options gl_video_conf = {
.opts = (const m_option_t[]) {
- OPT_CHOICE("opengl-dumb-mode", dumb_mode, 0,
+ OPT_CHOICE("gpu-dumb-mode", dumb_mode, 0,
({"auto", 0}, {"yes", 1}, {"no", -1})),
- OPT_FLOATRANGE("opengl-gamma", gamma, 0, 0.1, 2.0),
+ OPT_FLOATRANGE("gamma-factor", gamma, 0, 0.1, 2.0),
OPT_FLAG("gamma-auto", gamma_auto, 0),
OPT_CHOICE_C("target-prim", target_prim, 0, mp_csp_prim_names),
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
@@ -376,7 +374,7 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("sigmoid-upscaling", sigmoid_upscaling, 0),
OPT_FLOATRANGE("sigmoid-center", sigmoid_center, 0, 0.0, 1.0),
OPT_FLOATRANGE("sigmoid-slope", sigmoid_slope, 0, 1.0, 20.0),
- OPT_STRING("opengl-fbo-format", fbo_format, 0),
+ OPT_STRING("fbo-format", fbo_format, 0),
OPT_CHOICE_OR_INT("dither-depth", dither_depth, 0, -1, 16,
({"no", -1}, {"auto", 0})),
OPT_CHOICE("dither", dither_algo, 0,
@@ -399,18 +397,28 @@ const struct m_sub_options gl_video_conf = {
({"no", BLEND_SUBS_NO},
{"yes", BLEND_SUBS_YES},
{"video", BLEND_SUBS_VIDEO})),
- OPT_PATHLIST("opengl-shaders", user_shaders, 0),
- OPT_CLI_ALIAS("opengl-shader", "opengl-shaders-append"),
+ OPT_PATHLIST("glsl-shaders", user_shaders, 0),
+ OPT_CLI_ALIAS("glsl-shader", "glsl-shaders-append"),
OPT_FLAG("deband", deband, 0),
OPT_SUBSTRUCT("deband", deband_opts, deband_conf, 0),
OPT_FLOAT("sharpen", unsharp, 0),
- OPT_INTRANGE("opengl-tex-pad-x", tex_pad_x, 0, 0, 4096),
- OPT_INTRANGE("opengl-tex-pad-y", tex_pad_y, 0, 0, 4096),
+ OPT_INTRANGE("gpu-tex-pad-x", tex_pad_x, 0, 0, 4096),
+ OPT_INTRANGE("gpu-tex-pad-y", tex_pad_y, 0, 0, 4096),
OPT_SUBSTRUCT("", icc_opts, mp_icc_conf, 0),
- OPT_CHOICE("opengl-early-flush", early_flush, 0,
- ({"no", 0}, {"yes", 1}, {"auto", -1})),
- OPT_STRING("opengl-shader-cache-dir", shader_cache_dir, 0),
+ OPT_STRING("gpu-shader-cache-dir", shader_cache_dir, 0),
+ OPT_STRING_VALIDATE("gpu-hwdec-interop", hwdec_interop, 0,
+ ra_hwdec_validate_opt),
+ OPT_REPLACED("opengl-hwdec-interop", "gpu-hwdec-interop"),
+ OPT_REPLACED("hwdec-preload", "opengl-hwdec-interop"),
OPT_REPLACED("hdr-tone-mapping", "tone-mapping"),
+ OPT_REPLACED("opengl-shaders", "glsl-shaders"),
+ OPT_REPLACED("opengl-shader", "glsl-shader"),
+ OPT_REPLACED("opengl-shader-cache-dir", "gpu-shader-cache-dir"),
+ OPT_REPLACED("opengl-tex-pad-x", "gpu-tex-pad-x"),
+ OPT_REPLACED("opengl-tex-pad-y", "gpu-tex-pad-y"),
+ OPT_REPLACED("opengl-fbo-format", "fbo-format"),
+ OPT_REPLACED("opengl-dumb-mode", "gpu-dumb-mode"),
+ OPT_REPLACED("opengl-gamma", "gamma-factor"),
{0}
},
.size = sizeof(struct gl_video_opts),
@@ -425,6 +433,7 @@ static const char *handle_scaler_opt(const char *name, bool tscale);
static void reinit_from_options(struct gl_video *p);
static void get_scale_factors(struct gl_video *p, bool transpose_rot, double xy[2]);
static void gl_video_setup_hooks(struct gl_video *p);
+static void gl_video_update_options(struct gl_video *p);
#define GLSL(x) gl_sc_add(p->sc, #x "\n");
#define GLSLF(...) gl_sc_addf(p->sc, __VA_ARGS__)
@@ -460,32 +469,32 @@ static void debug_check_gl(struct gl_video *p, const char *msg)
static void gl_video_reset_surfaces(struct gl_video *p)
{
- for (int i = 0; i < FBOSURFACES_MAX; i++) {
+ for (int i = 0; i < SURFACES_MAX; i++) {
p->surfaces[i].id = 0;
p->surfaces[i].pts = MP_NOPTS_VALUE;
}
p->surface_idx = 0;
p->surface_now = 0;
p->frames_drawn = 0;
- p->output_fbo_valid = false;
+ p->output_tex_valid = false;
}
static void gl_video_reset_hooks(struct gl_video *p)
{
- for (int i = 0; i < p->tex_hook_num; i++)
+ for (int i = 0; i < p->num_tex_hooks; i++)
talloc_free(p->tex_hooks[i].priv);
- for (int i = 0; i < p->user_tex_num; i++)
+ for (int i = 0; i < p->num_user_textures; i++)
ra_tex_free(p->ra, &p->user_textures[i].tex);
- p->tex_hook_num = 0;
- p->user_tex_num = 0;
+ p->num_tex_hooks = 0;
+ p->num_user_textures = 0;
}
-static inline int fbosurface_wrap(int id)
+static inline int surface_wrap(int id)
{
- id = id % FBOSURFACES_MAX;
- return id < 0 ? id + FBOSURFACES_MAX : id;
+ id = id % SURFACES_MAX;
+ return id < 0 ? id + SURFACES_MAX : id;
}
static void reinit_osd(struct gl_video *p)
@@ -504,24 +513,24 @@ static void uninit_rendering(struct gl_video *p)
ra_tex_free(p->ra, &p->dither_texture);
for (int n = 0; n < 4; n++) {
- fbotex_uninit(&p->merge_fbo[n]);
- fbotex_uninit(&p->scale_fbo[n]);
- fbotex_uninit(&p->integer_fbo[n]);
+ ra_tex_free(p->ra, &p->merge_tex[n]);
+ ra_tex_free(p->ra, &p->scale_tex[n]);
+ ra_tex_free(p->ra, &p->integer_tex[n]);
}
- fbotex_uninit(&p->indirect_fbo);
- fbotex_uninit(&p->blend_subs_fbo);
- fbotex_uninit(&p->screen_fbo);
- fbotex_uninit(&p->output_fbo);
+ ra_tex_free(p->ra, &p->indirect_tex);
+ ra_tex_free(p->ra, &p->blend_subs_tex);
+ ra_tex_free(p->ra, &p->screen_tex);
+ ra_tex_free(p->ra, &p->output_tex);
- for (int n = 0; n < FBOSURFACES_MAX; n++)
- fbotex_uninit(&p->surfaces[n].fbotex);
+ for (int n = 0; n < SURFACES_MAX; n++)
+ ra_tex_free(p->ra, &p->surfaces[n].tex);
- for (int n = 0; n < SHADER_MAX_SAVED; n++)
- fbotex_uninit(&p->hook_fbos[n]);
+ for (int n = 0; n < p->num_hook_textures; n++)
+ ra_tex_free(p->ra, &p->hook_textures[n]);
for (int n = 0; n < 2; n++)
- fbotex_uninit(&p->vdpau_deinterleave_fbo[n]);
+ ra_tex_free(p->ra, &p->vdpau_deinterleave_tex[n]);
gl_video_reset_surfaces(p);
gl_video_reset_hooks(p);
@@ -607,29 +616,28 @@ static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim,
return true;
}
-// Fill an img_tex struct from an FBO + some metadata
-static struct img_tex img_tex_fbo(struct fbotex *fbo, enum plane_type type,
- int components)
+// Fill an image struct from a ra_tex + some metadata
+static struct image image_wrap(struct ra_tex *tex, enum plane_type type,
+ int components)
{
assert(type != PLANE_NONE);
- return (struct img_tex){
+ return (struct image){
.type = type,
- .tex = fbo->tex,
+ .tex = tex,
.multiplier = 1.0,
- .w = fbo->lw,
- .h = fbo->lh,
+ .w = tex ? tex->params.w : 1,
+ .h = tex ? tex->params.h : 1,
.transform = identity_trans,
.components = components,
};
}
-// Bind an img_tex to a free texture unit and return its ID. At most
-// TEXUNIT_VIDEO_NUM texture units can be bound at once
-static int pass_bind(struct gl_video *p, struct img_tex tex)
+// Bind an image to a free texture unit and return its ID.
+static int pass_bind(struct gl_video *p, struct image img)
{
- assert(p->pass_tex_num < TEXUNIT_VIDEO_NUM);
- p->pass_tex[p->pass_tex_num] = tex;
- return p->pass_tex_num++;
+ int idx = p->num_pass_imgs;
+ MP_TARRAY_APPEND(p, p->pass_imgs, p->num_pass_imgs, img);
+ return idx;
}
// Rotation by 90° and flipping.
@@ -678,11 +686,11 @@ static enum plane_type merge_plane_types(enum plane_type a, enum plane_type b)
return a;
}
-// Places a video_image's image textures + associated metadata into tex[]. The
+// Places a video_image's image textures + associated metadata into img[]. The
// number of textures is equal to p->plane_count. Any necessary plane offsets
// are stored in off. (e.g. chroma position)
-static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg,
- struct img_tex tex[4], struct gl_transform off[4])
+static void pass_get_images(struct gl_video *p, struct video_image *vimg,
+ struct image img[4], struct gl_transform off[4])
{
assert(vimg->mpi);
@@ -715,7 +723,7 @@ static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg,
msb_valid_bits,
p->ra_format.component_bits);
- memset(tex, 0, 4 * sizeof(tex[0]));
+ memset(img, 0, 4 * sizeof(img[0]));
for (int n = 0; n < p->plane_count; n++) {
struct texplane *t = &vimg->planes[n];
@@ -737,7 +745,7 @@ static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg,
type = merge_plane_types(type, ctype);
}
- tex[n] = (struct img_tex){
+ img[n] = (struct image){
.type = type,
.tex = t->tex,
.multiplier = tex_mul,
@@ -746,12 +754,12 @@ static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg,
};
for (int i = 0; i < 4; i++)
- tex[n].components += !!p->ra_format.components[n][i];
+ img[n].components += !!p->ra_format.components[n][i];
get_transform(t->w, t->h, p->image_params.rotate, t->flipped,
- &tex[n].transform);
+ &img[n].transform);
if (p->image_params.rotate % 180 == 90)
- MPSWAP(int, tex[n].w, tex[n].h);
+ MPSWAP(int, img[n].w, img[n].h);
off[n] = identity_trans;
@@ -804,18 +812,27 @@ static void init_video(struct gl_video *p)
{
p->use_integer_conversion = false;
- if (p->hwdec && ra_hwdec_test_format(p->hwdec, p->image_params.imgfmt)) {
- if (p->hwdec->driver->overlay_frame) {
+ struct ra_hwdec *hwdec = NULL;
+ for (int n = 0; n < p->num_hwdecs; n++) {
+ if (ra_hwdec_test_format(p->hwdecs[n], p->image_params.imgfmt)) {
+ hwdec = p->hwdecs[n];
+ break;
+ }
+ }
+
+ if (hwdec) {
+ if (hwdec->driver->overlay_frame) {
MP_WARN(p, "Using HW-overlay mode. No GL filtering is performed "
"on the video!\n");
+ p->hwdec_overlay = hwdec;
} else {
- p->hwdec_mapper = ra_hwdec_mapper_create(p->hwdec, &p->image_params);
+ p->hwdec_mapper = ra_hwdec_mapper_create(hwdec, &p->image_params);
if (!p->hwdec_mapper)
MP_ERR(p, "Initializing texture for hardware decoding failed.\n");
}
if (p->hwdec_mapper)
p->image_params = p->hwdec_mapper->dst_params;
- const char **exts = p->hwdec->glsl_extensions;
+ const char **exts = hwdec->glsl_extensions;
for (int n = 0; exts && exts[n]; n++)
gl_sc_enable_extension(p->sc, (char *)exts[n]);
p->hwdec_active = true;
@@ -895,20 +912,6 @@ static void init_video(struct gl_video *p)
gl_video_setup_hooks(p);
}
-// Release any texture mappings associated with the current frame.
-static void unmap_current_image(struct gl_video *p)
-{
- struct video_image *vimg = &p->image;
-
- if (vimg->hwdec_mapped) {
- assert(p->hwdec_active && p->hwdec_mapper);
- ra_hwdec_mapper_unmap(p->hwdec_mapper);
- memset(vimg->planes, 0, sizeof(vimg->planes));
- vimg->hwdec_mapped = false;
- vimg->id = 0; // needs to be mapped again
- }
-}
-
static struct dr_buffer *gl_find_dr_buffer(struct gl_video *p, uint8_t *ptr)
{
for (int i = 0; i < p->num_dr_buffers; i++) {
@@ -949,10 +952,18 @@ again:;
static void unref_current_image(struct gl_video *p)
{
- unmap_current_image(p);
- p->image.id = 0;
+ struct video_image *vimg = &p->image;
+
+ if (vimg->hwdec_mapped) {
+ assert(p->hwdec_active && p->hwdec_mapper);
+ ra_hwdec_mapper_unmap(p->hwdec_mapper);
+ memset(vimg->planes, 0, sizeof(vimg->planes));
+ vimg->hwdec_mapped = false;
+ }
+
+ vimg->id = 0;
- mp_image_unrefp(&p->image.mpi);
+ mp_image_unrefp(&vimg->mpi);
// While we're at it, also garbage collect pending fences in here to
// get it out of the way.
@@ -964,8 +975,8 @@ static void unref_current_image(struct gl_video *p)
// lead to flickering artifacts.
static void unmap_overlay(struct gl_video *p)
{
- if (p->hwdec_active && p->hwdec->driver->overlay_frame)
- p->hwdec->driver->overlay_frame(p->hwdec, NULL, NULL, NULL, true);
+ if (p->hwdec_overlay)
+ p->hwdec_overlay->driver->overlay_frame(p->hwdec_overlay, NULL, NULL, NULL, true);
}
static void uninit_video(struct gl_video *p)
@@ -988,12 +999,13 @@ static void uninit_video(struct gl_video *p)
p->real_image_params = (struct mp_image_params){0};
p->image_params = p->real_image_params;
p->hwdec_active = false;
+ p->hwdec_overlay = NULL;
ra_hwdec_mapper_free(&p->hwdec_mapper);
}
static void pass_record(struct gl_video *p, struct mp_pass_perf perf)
{
- if (!p->pass || p->pass_idx == PASS_INFO_MAX)
+ if (!p->pass || p->pass_idx == VO_PASS_PERF_MAX)
return;
struct pass_info *pass = &p->pass[p->pass_idx];
@@ -1008,7 +1020,7 @@ static void pass_record(struct gl_video *p, struct mp_pass_perf perf)
PRINTF_ATTRIBUTE(2, 3)
static void pass_describe(struct gl_video *p, const char *textf, ...)
{
- if (!p->pass || p->pass_idx == PASS_INFO_MAX)
+ if (!p->pass || p->pass_idx == VO_PASS_PERF_MAX)
return;
struct pass_info *pass = &p->pass[p->pass_idx];
@@ -1027,7 +1039,7 @@ static void pass_info_reset(struct gl_video *p, bool is_redraw)
p->pass = is_redraw ? p->pass_redraw : p->pass_fresh;
p->pass_idx = 0;
- for (int i = 0; i < PASS_INFO_MAX; i++) {
+ for (int i = 0; i < VO_PASS_PERF_MAX; i++) {
p->pass[i].desc.len = 0;
p->pass[i].perf = (struct mp_pass_perf){0};
}
@@ -1038,14 +1050,14 @@ static void pass_report_performance(struct gl_video *p)
if (!p->pass)
return;
- for (int i = 0; i < PASS_INFO_MAX; i++) {
+ for (int i = 0; i < VO_PASS_PERF_MAX; i++) {
struct pass_info *pass = &p->pass[i];
if (pass->desc.len) {
- MP_DBG(p, "pass '%.*s': last %dus avg %dus peak %dus\n",
- BSTR_P(pass->desc),
- (int)pass->perf.last/1000,
- (int)pass->perf.avg/1000,
- (int)pass->perf.peak/1000);
+ MP_TRACE(p, "pass '%.*s': last %dus avg %dus peak %dus\n",
+ BSTR_P(pass->desc),
+ (int)pass->perf.last/1000,
+ (int)pass->perf.avg/1000,
+ (int)pass->perf.peak/1000);
}
}
}
@@ -1054,8 +1066,8 @@ static void pass_prepare_src_tex(struct gl_video *p)
{
struct gl_shader_cache *sc = p->sc;
- for (int n = 0; n < p->pass_tex_num; n++) {
- struct img_tex *s = &p->pass_tex[n];
+ for (int n = 0; n < p->num_pass_imgs; n++) {
+ struct image *s = &p->pass_imgs[n];
if (!s->tex)
continue;
@@ -1079,6 +1091,11 @@ static void pass_prepare_src_tex(struct gl_video *p)
}
}
+static void cleanup_binds(struct gl_video *p)
+{
+ p->num_pass_imgs = 0;
+}
+
// Sets the appropriate compute shader metadata for an implicit compute pass
// bw/bh: block size
static void pass_is_compute(struct gl_video *p, int bw, int bh)
@@ -1101,7 +1118,6 @@ static void dispatch_compute(struct gl_video *p, int w, int h,
info.threads_h > 0 ? info.threads_h : info.block_h);
pass_prepare_src_tex(p);
- gl_sc_set_vertex_format(p->sc, vertex_vao, sizeof(struct vertex));
// Since we don't actually have vertices, we pretend for convenience
// reasons that we do and calculate the right texture coordinates based on
@@ -1109,25 +1125,21 @@ static void dispatch_compute(struct gl_video *p, int w, int h,
gl_sc_uniform_vec2(p->sc, "out_scale", (float[2]){ 1.0 / w, 1.0 / h });
PRELUDE("#define outcoord(id) (out_scale * (vec2(id) + vec2(0.5)))\n");
- for (int n = 0; n < TEXUNIT_VIDEO_NUM; n++) {
- struct img_tex *s = &p->pass_tex[n];
+ for (int n = 0; n < p->num_pass_imgs; n++) {
+ struct image *s = &p->pass_imgs[n];
if (!s->tex)
continue;
// We need to rescale the coordinates to the true texture size
- char tex_scale[32];
- snprintf(tex_scale, sizeof(tex_scale), "tex_scale%d", n);
+ char *tex_scale = mp_tprintf(32, "tex_scale%d", n);
gl_sc_uniform_vec2(p->sc, tex_scale, (float[2]){
(float)s->w / s->tex->params.w,
(float)s->h / s->tex->params.h,
});
- PRELUDE("#define texcoord%d_raw(id) (tex_scale%d * outcoord(id))\n", n, n);
- PRELUDE("#define texcoord%d_rot(id) (texture_rot%d * texcoord%d_raw(id) + "
+ PRELUDE("#define texmap%d_raw(id) (tex_scale%d * outcoord(id))\n", n, n);
+ PRELUDE("#define texmap%d(id) (texture_rot%d * texmap%d_raw(id) + "
"pixel_size%d * texture_off%d)\n", n, n, n, n, n);
- // Clamp the texture coordinates to prevent sampling out-of-bounds in
- // threads that exceed the requested width/height
- PRELUDE("#define texmap%d(id) min(texcoord%d_rot(id), vec2(1.0))\n", n, n);
PRELUDE("#define texcoord%d texmap%d(gl_GlobalInvocationID)\n", n, n);
}
@@ -1137,19 +1149,34 @@ static void dispatch_compute(struct gl_video *p, int w, int h,
num_y = info.block_h > 0 ? (h + info.block_h - 1) / info.block_h : 1;
pass_record(p, gl_sc_dispatch_compute(p->sc, num_x, num_y, 1));
-
- memset(&p->pass_tex, 0, sizeof(p->pass_tex));
- p->pass_tex_num = 0;
+ cleanup_binds(p);
}
static struct mp_pass_perf render_pass_quad(struct gl_video *p,
- struct fbodst target,
+ struct ra_fbo fbo,
const struct mp_rect *dst)
{
- struct vertex va[6] = {0};
+ // The first element is reserved for `vec2 position`
+ int num_vertex_attribs = 1 + p->num_pass_imgs;
+ size_t vertex_stride = num_vertex_attribs * sizeof(struct vertex_pt);
+
+ // Expand the VAO if necessary
+ while (p->vao_len < num_vertex_attribs) {
+ MP_TARRAY_APPEND(p, p->vao, p->vao_len, (struct ra_renderpass_input) {
+ .name = talloc_asprintf(p, "texcoord%d", p->vao_len - 1),
+ .type = RA_VARTYPE_FLOAT,
+ .dim_v = 2,
+ .dim_m = 1,
+ .offset = p->vao_len * sizeof(struct vertex_pt),
+ });
+ }
+
+ int num_vertices = 6; // quad as triangle list
+ int num_attribs_total = num_vertices * num_vertex_attribs;
+ MP_TARRAY_GROW(p, p->tmp_vertex, num_attribs_total);
struct gl_transform t;
- gl_transform_ortho_fbodst(&t, target);
+ gl_transform_ortho_fbo(&t, fbo);
float x[2] = {dst->x0, dst->x1};
float y[2] = {dst->y0, dst->y1};
@@ -1157,11 +1184,12 @@ static struct mp_pass_perf render_pass_quad(struct gl_video *p,
gl_transform_vec(t, &x[1], &y[1]);
for (int n = 0; n < 4; n++) {
- struct vertex *v = &va[n];
- v->position.x = x[n / 2];
- v->position.y = y[n % 2];
- for (int i = 0; i < p->pass_tex_num; i++) {
- struct img_tex *s = &p->pass_tex[i];
+ struct vertex_pt *vs = &p->tmp_vertex[num_vertex_attribs * n];
+ // vec2 position in idx 0
+ vs[0].x = x[n / 2];
+ vs[0].y = y[n % 2];
+ for (int i = 0; i < p->num_pass_imgs; i++) {
+ struct image *s = &p->pass_imgs[i];
if (!s->tex)
continue;
struct gl_transform tr = s->transform;
@@ -1169,43 +1197,48 @@ static struct mp_pass_perf render_pass_quad(struct gl_video *p,
float ty = (n % 2) * s->h;
gl_transform_vec(tr, &tx, &ty);
bool rect = s->tex->params.non_normalized;
- v->texcoord[i].x = tx / (rect ? 1 : s->tex->params.w);
- v->texcoord[i].y = ty / (rect ? 1 : s->tex->params.h);
+ // vec2 texcoordN in idx N+1
+ vs[i + 1].x = tx / (rect ? 1 : s->tex->params.w);
+ vs[i + 1].y = ty / (rect ? 1 : s->tex->params.h);
}
}
- va[4] = va[2];
- va[5] = va[1];
+ memmove(&p->tmp_vertex[num_vertex_attribs * 4],
+ &p->tmp_vertex[num_vertex_attribs * 2],
+ vertex_stride);
+
+ memmove(&p->tmp_vertex[num_vertex_attribs * 5],
+ &p->tmp_vertex[num_vertex_attribs * 1],
+ vertex_stride);
- return gl_sc_dispatch_draw(p->sc, target.tex, va, 6);
+ return gl_sc_dispatch_draw(p->sc, fbo.tex, p->vao, num_vertex_attribs,
+ vertex_stride, p->tmp_vertex, num_vertices);
}
-static void finish_pass_direct(struct gl_video *p, struct fbodst target,
- const struct mp_rect *dst)
+static void finish_pass_fbo(struct gl_video *p, struct ra_fbo fbo,
+ const struct mp_rect *dst)
{
pass_prepare_src_tex(p);
- gl_sc_set_vertex_format(p->sc, vertex_vao, sizeof(struct vertex));
- pass_record(p, render_pass_quad(p, target, dst));
+ pass_record(p, render_pass_quad(p, fbo, dst));
debug_check_gl(p, "after rendering");
- memset(&p->pass_tex, 0, sizeof(p->pass_tex));
- p->pass_tex_num = 0;
+ cleanup_binds(p);
}
// dst_fbo: this will be used for rendering; possibly reallocating the whole
// FBO, if the required parameters have changed
// w, h: required FBO target dimension, and also defines the target rectangle
// used for rasterization
-// flags: 0 or combination of FBOTEX_FUZZY_W/FBOTEX_FUZZY_H (setting the fuzzy
-// flags allows the FBO to be larger than the w/h parameters)
-static void finish_pass_fbo(struct gl_video *p, struct fbotex *dst_fbo,
- int w, int h, int flags)
+static void finish_pass_tex(struct gl_video *p, struct ra_tex **dst_tex,
+ int w, int h)
{
- fbotex_change(dst_fbo, p->ra, p->log, w, h, p->fbo_format, flags);
+ if (!ra_tex_resize(p->ra, p->log, dst_tex, w, h, p->fbo_format)) {
+ cleanup_binds(p);
+ gl_sc_reset(p->sc);
+ return;
+ }
if (p->pass_compute.active) {
- if (!dst_fbo->tex)
- return;
- gl_sc_uniform_image2D_wo(p->sc, "out_image", dst_fbo->tex);
+ gl_sc_uniform_image2D_wo(p->sc, "out_image", *dst_tex);
if (!p->pass_compute.directly_writes)
GLSL(imageStore(out_image, ivec2(gl_GlobalInvocationID), color);)
@@ -1214,11 +1247,12 @@ static void finish_pass_fbo(struct gl_video *p, struct fbotex *dst_fbo,
debug_check_gl(p, "after dispatching compute shader");
} else {
- finish_pass_direct(p, dst_fbo->fbo, &(struct mp_rect){0, 0, w, h});
+ struct ra_fbo fbo = { .tex = *dst_tex, };
+ finish_pass_fbo(p, fbo, &(struct mp_rect){0, 0, w, h});
}
}
-static const char *get_tex_swizzle(struct img_tex *img)
+static const char *get_tex_swizzle(struct image *img)
{
if (!img->tex)
return "rgba";
@@ -1227,7 +1261,7 @@ static const char *get_tex_swizzle(struct img_tex *img)
// Copy a texture to the vec4 color, while increasing offset. Also applies
// the texture multiplier to the sampled color
-static void copy_img_tex(struct gl_video *p, int *offset, struct img_tex img)
+static void copy_image(struct gl_video *p, int *offset, struct image img)
{
int count = img.components;
assert(*offset + count <= 4);
@@ -1261,14 +1295,14 @@ static void skip_unused(struct gl_video *p, int num_components)
static void uninit_scaler(struct gl_video *p, struct scaler *scaler)
{
- fbotex_uninit(&scaler->sep_fbo);
+ ra_tex_free(p->ra, &scaler->sep_fbo);
ra_tex_free(p->ra, &scaler->lut);
scaler->kernel = NULL;
scaler->initialized = false;
}
static void hook_prelude(struct gl_video *p, const char *name, int id,
- struct img_tex tex)
+ struct image img)
{
GLSLHF("#define %s_raw texture%d\n", name, id);
GLSLHF("#define %s_pos texcoord%d\n", name, id);
@@ -1276,15 +1310,15 @@ static void hook_prelude(struct gl_video *p, const char *name, int id,
GLSLHF("#define %s_rot texture_rot%d\n", name, id);
GLSLHF("#define %s_pt pixel_size%d\n", name, id);
GLSLHF("#define %s_map texmap%d\n", name, id);
- GLSLHF("#define %s_mul %f\n", name, tex.multiplier);
+ GLSLHF("#define %s_mul %f\n", name, img.multiplier);
// Set up the sampling functions
GLSLHF("#define %s_tex(pos) (%s_mul * vec4(texture(%s_raw, pos)).%s)\n",
- name, name, name, get_tex_swizzle(&tex));
+ name, name, name, get_tex_swizzle(&img));
// Since the extra matrix multiplication impacts performance,
// skip it unless the texture was actually rotated
- if (gl_transform_eq(tex.transform, identity_trans)) {
+ if (gl_transform_eq(img.transform, identity_trans)) {
GLSLHF("#define %s_texOff(off) %s_tex(%s_pos + %s_pt * vec2(off))\n",
name, name, name, name);
} else {
@@ -1294,15 +1328,15 @@ static void hook_prelude(struct gl_video *p, const char *name, int id,
}
}
-static bool saved_tex_find(struct gl_video *p, const char *name,
- struct img_tex *out)
+static bool saved_img_find(struct gl_video *p, const char *name,
+ struct image *out)
{
if (!name || !out)
return false;
- for (int i = 0; i < p->saved_tex_num; i++) {
- if (strcmp(p->saved_tex[i].name, name) == 0) {
- *out = p->saved_tex[i].tex;
+ for (int i = 0; i < p->num_saved_imgs; i++) {
+ if (strcmp(p->saved_imgs[i].name, name) == 0) {
+ *out = p->saved_imgs[i].img;
return true;
}
}
@@ -1310,29 +1344,28 @@ static bool saved_tex_find(struct gl_video *p, const char *name,
return false;
}
-static void saved_tex_store(struct gl_video *p, const char *name,
- struct img_tex tex)
+static void saved_img_store(struct gl_video *p, const char *name,
+ struct image img)
{
assert(name);
- for (int i = 0; i < p->saved_tex_num; i++) {
- if (strcmp(p->saved_tex[i].name, name) == 0) {
- p->saved_tex[i].tex = tex;
+ for (int i = 0; i < p->num_saved_imgs; i++) {
+ if (strcmp(p->saved_imgs[i].name, name) == 0) {
+ p->saved_imgs[i].img = img;
return;
}
}
- assert(p->saved_tex_num < SHADER_MAX_SAVED);
- p->saved_tex[p->saved_tex_num++] = (struct saved_tex) {
+ MP_TARRAY_APPEND(p, p->saved_imgs, p->num_saved_imgs, (struct saved_img) {
.name = name,
- .tex = tex
- };
+ .img = img
+ });
}
static bool pass_hook_setup_binds(struct gl_video *p, const char *name,
- struct img_tex tex, struct tex_hook *hook)
+ struct image img, struct tex_hook *hook)
{
- for (int t = 0; t < TEXUNIT_VIDEO_NUM; t++) {
+ for (int t = 0; t < SHADER_MAX_BINDS; t++) {
char *bind_name = (char *)hook->bind_tex[t];
if (!bind_name)
@@ -1340,16 +1373,16 @@ static bool pass_hook_setup_binds(struct gl_video *p, const char *name,
// This is a special name that means "currently hooked texture"
if (strcmp(bind_name, "HOOKED") == 0) {
- int id = pass_bind(p, tex);
- hook_prelude(p, "HOOKED", id, tex);
- hook_prelude(p, name, id, tex);
+ int id = pass_bind(p, img);
+ hook_prelude(p, "HOOKED", id, img);
+ hook_prelude(p, name, id, img);
continue;
}
// BIND can also be used to load user-defined textures, in which
// case we will directly load them as a uniform instead of
// generating the hook_prelude boilerplate
- for (int u = 0; u < p->user_tex_num; u++) {
+ for (int u = 0; u < p->num_user_textures; u++) {
struct gl_user_shader_tex *utex = &p->user_textures[u];
if (bstr_equals0(utex->name, bind_name)) {
gl_sc_uniform_texture(p->sc, bind_name, utex->tex);
@@ -1357,16 +1390,16 @@ static bool pass_hook_setup_binds(struct gl_video *p, const char *name,
}
}
- struct img_tex bind_tex;
- if (!saved_tex_find(p, bind_name, &bind_tex)) {
+ struct image bind_img;
+ if (!saved_img_find(p, bind_name, &bind_img)) {
// Clean up texture bindings and move on to the next hook
- MP_DBG(p, "Skipping hook on %s due to no texture named %s.\n",
- name, bind_name);
- p->pass_tex_num -= t;
+ MP_TRACE(p, "Skipping hook on %s due to no texture named %s.\n",
+ name, bind_name);
+ p->num_pass_imgs -= t;
return false;
}
- hook_prelude(p, bind_name, pass_bind(p, bind_tex), bind_tex);
+ hook_prelude(p, bind_name, pass_bind(p, bind_img), bind_img);
next_bind: ;
}
@@ -1374,18 +1407,26 @@ next_bind: ;
return true;
}
-// Process hooks for a plane, saving the result and returning a new img_tex
-// If 'trans' is NULL, the shader is forbidden from transforming tex
-static struct img_tex pass_hook(struct gl_video *p, const char *name,
- struct img_tex tex, struct gl_transform *trans)
+static struct ra_tex **next_hook_tex(struct gl_video *p)
+{
+ if (p->idx_hook_textures == p->num_hook_textures)
+ MP_TARRAY_APPEND(p, p->hook_textures, p->num_hook_textures, NULL);
+
+ return &p->hook_textures[p->idx_hook_textures++];
+}
+
+// Process hooks for a plane, saving the result and returning a new image
+// If 'trans' is NULL, the shader is forbidden from transforming img
+static struct image pass_hook(struct gl_video *p, const char *name,
+ struct image img, struct gl_transform *trans)
{
if (!name)
- return tex;
+ return img;
- saved_tex_store(p, name, tex);
+ saved_img_store(p, name, img);
- MP_DBG(p, "Running hooks for %s\n", name);
- for (int i = 0; i < p->tex_hook_num; i++) {
+ MP_TRACE(p, "Running hooks for %s\n", name);
+ for (int i = 0; i < p->num_tex_hooks; i++) {
struct tex_hook *hook = &p->tex_hooks[i];
// Figure out if this pass hooks this texture
@@ -1398,34 +1439,32 @@ static struct img_tex pass_hook(struct gl_video *p, const char *name,
found:
// Check the hook's condition
- if (hook->cond && !hook->cond(p, tex, hook->priv)) {
- MP_DBG(p, "Skipping hook on %s due to condition.\n", name);
+ if (hook->cond && !hook->cond(p, img, hook->priv)) {
+ MP_TRACE(p, "Skipping hook on %s due to condition.\n", name);
continue;
}
- if (!pass_hook_setup_binds(p, name, tex, hook))
+ if (!pass_hook_setup_binds(p, name, img, hook))
continue;
// Run the actual hook. This generates a series of GLSL shader
// instructions sufficient for drawing the hook's output
struct gl_transform hook_off = identity_trans;
- hook->hook(p, tex, &hook_off, hook->priv);
+ hook->hook(p, img, &hook_off, hook->priv);
- int comps = hook->components ? hook->components : tex.components;
+ int comps = hook->components ? hook->components : img.components;
skip_unused(p, comps);
// Compute the updated FBO dimensions and store the result
- struct mp_rect_f sz = {0, 0, tex.w, tex.h};
+ struct mp_rect_f sz = {0, 0, img.w, img.h};
gl_transform_rect(hook_off, &sz);
int w = lroundf(fabs(sz.x1 - sz.x0));
int h = lroundf(fabs(sz.y1 - sz.y0));
- assert(p->hook_fbo_num < SHADER_MAX_SAVED);
- struct fbotex *fbo = &p->hook_fbos[p->hook_fbo_num++];
- finish_pass_fbo(p, fbo, w, h, 0);
-
+ struct ra_tex **tex = next_hook_tex(p);
+ finish_pass_tex(p, tex, w, h);
const char *store_name = hook->save_tex ? hook->save_tex : name;
- struct img_tex saved_tex = img_tex_fbo(fbo, tex.type, comps);
+ struct image saved_img = image_wrap(*tex, img.type, comps);
// If the texture we're saving overwrites the "current" texture, also
// update the tex parameter so that the future loop cycles will use the
@@ -1434,18 +1473,18 @@ found:
if (!trans && !gl_transform_eq(hook_off, identity_trans)) {
MP_ERR(p, "Hook tried changing size of unscalable texture %s!\n",
name);
- return tex;
+ return img;
}
- tex = saved_tex;
+ img = saved_img;
if (trans)
gl_transform_trans(hook_off, trans);
}
- saved_tex_store(p, store_name, saved_tex);
+ saved_img_store(p, store_name, saved_img);
}
- return tex;
+ return img;
}
// This can be used at any time in the middle of rendering to specify an
@@ -1459,7 +1498,7 @@ static void pass_opt_hook_point(struct gl_video *p, const char *name,
if (!name)
return;
- for (int i = 0; i < p->tex_hook_num; i++) {
+ for (int i = 0; i < p->num_tex_hooks; i++) {
struct tex_hook *hook = &p->tex_hooks[i];
for (int h = 0; h < SHADER_MAX_HOOKS; h++) {
@@ -1467,7 +1506,7 @@ static void pass_opt_hook_point(struct gl_video *p, const char *name,
goto found;
}
- for (int b = 0; b < TEXUNIT_VIDEO_NUM; b++) {
+ for (int b = 0; b < SHADER_MAX_BINDS; b++) {
if (hook->bind_tex[b] && strcmp(hook->bind_tex[b], name) == 0)
goto found;
}
@@ -1476,14 +1515,12 @@ static void pass_opt_hook_point(struct gl_video *p, const char *name,
// Nothing uses this texture, don't bother storing it
return;
-found:
- assert(p->hook_fbo_num < SHADER_MAX_SAVED);
- struct fbotex *fbo = &p->hook_fbos[p->hook_fbo_num++];
- finish_pass_fbo(p, fbo, p->texture_w, p->texture_h, 0);
-
- struct img_tex img = img_tex_fbo(fbo, PLANE_RGB, p->components);
+found: ;
+ struct ra_tex **tex = next_hook_tex(p);
+ finish_pass_tex(p, tex, p->texture_w, p->texture_h);
+ struct image img = image_wrap(*tex, PLANE_RGB, p->components);
img = pass_hook(p, name, img, tex_trans);
- copy_img_tex(p, &(int){0}, img);
+ copy_image(p, &(int){0}, img);
p->texture_w = img.w;
p->texture_h = img.h;
p->components = img.components;
@@ -1493,7 +1530,9 @@ found:
static void load_shader(struct gl_video *p, struct bstr body)
{
gl_sc_hadd_bstr(p->sc, body);
+ gl_sc_uniform_dynamic(p->sc);
gl_sc_uniform_f(p->sc, "random", (double)av_lfg_get(&p->lfg) / UINT32_MAX);
+ gl_sc_uniform_dynamic(p->sc);
gl_sc_uniform_i(p->sc, "frame", p->frames_uploaded);
gl_sc_uniform_vec2(p->sc, "input_size",
(float[]){(p->src_rect.x1 - p->src_rect.x0) *
@@ -1631,7 +1670,7 @@ static void reinit_scaler(struct gl_video *p, struct scaler *scaler,
}
// Special helper for sampling from two separated stages
-static void pass_sample_separated(struct gl_video *p, struct img_tex src,
+static void pass_sample_separated(struct gl_video *p, struct image src,
struct scaler *scaler, int w, int h)
{
// Separate the transformation into x and y components, per pass
@@ -1650,10 +1689,10 @@ static void pass_sample_separated(struct gl_video *p, struct img_tex src,
GLSLF("// first pass\n");
pass_sample_separated_gen(p->sc, scaler, 0, 1);
GLSLF("color *= %f;\n", src.multiplier);
- finish_pass_fbo(p, &scaler->sep_fbo, src.w, h, FBOTEX_FUZZY_H);
+ finish_pass_tex(p, &scaler->sep_fbo, src.w, h);
// Second pass (scale only in the x dir)
- src = img_tex_fbo(&scaler->sep_fbo, src.type, src.components);
+ src = image_wrap(scaler->sep_fbo, src.type, src.components);
src.transform = t_x;
pass_describe(p, "%s second pass", scaler->conf.kernel.name);
sampler_prelude(p->sc, pass_bind(p, src));
@@ -1663,9 +1702,9 @@ static void pass_sample_separated(struct gl_video *p, struct img_tex src,
// Picks either the compute shader version or the regular sampler version
// depending on hardware support
static void pass_dispatch_sample_polar(struct gl_video *p, struct scaler *scaler,
- struct img_tex tex, int w, int h)
+ struct image img, int w, int h)
{
- uint64_t reqs = RA_CAP_COMPUTE | RA_CAP_NESTED_ARRAY;
+ uint64_t reqs = RA_CAP_COMPUTE;
if ((p->ra->caps & reqs) != reqs)
goto fallback;
@@ -1673,8 +1712,8 @@ static void pass_dispatch_sample_polar(struct gl_video *p, struct scaler *scaler
int offset = bound - 1; // padding top/left
int padding = offset + bound; // total padding
- float ratiox = (float)w / tex.w,
- ratioy = (float)h / tex.h;
+ float ratiox = (float)w / img.w,
+ ratioy = (float)h / img.h;
// For performance we want to load at least as many pixels
// horizontally as there are threads in a warp (32 for nvidia), as
@@ -1688,27 +1727,28 @@ static void pass_dispatch_sample_polar(struct gl_video *p, struct scaler *scaler
int iw = (int)ceil(bw / ratiox) + padding + 1,
ih = (int)ceil(bh / ratioy) + padding + 1;
- int shmem_req = iw * ih * tex.components * sizeof(float);
+ int shmem_req = iw * ih * img.components * sizeof(float);
if (shmem_req > p->ra->max_shmem)
goto fallback;
pass_is_compute(p, bw, bh);
- pass_compute_polar(p->sc, scaler, tex.components, bw, bh, iw, ih);
+ pass_compute_polar(p->sc, scaler, img.components, bw, bh, iw, ih);
return;
fallback:
// Fall back to regular polar shader when compute shaders are unsupported
// or the kernel is too big for shmem
- pass_sample_polar(p->sc, scaler, tex.components, p->ra->glsl_version);
+ pass_sample_polar(p->sc, scaler, img.components,
+ p->ra->caps & RA_CAP_GATHER);
}
-// Sample from img_tex, with the src rectangle given by it.
+// Sample from image, with the src rectangle given by it.
// The dst rectangle is implicit by what the caller will do next, but w and h
// must still be what is going to be used (to dimension FBOs correctly).
// This will write the scaled contents to the vec4 "color".
// The scaler unit is initialized by this function; in order to avoid cache
// thrashing, the scaler unit should usually use the same parameters.
-static void pass_sample(struct gl_video *p, struct img_tex tex,
+static void pass_sample(struct gl_video *p, struct image img,
struct scaler *scaler, const struct scaler_config *conf,
double scale_factor, int w, int h)
{
@@ -1723,14 +1763,14 @@ static void pass_sample(struct gl_video *p, struct img_tex tex,
};
pass_describe(p, "%s=%s (%s)", scaler_opt[scaler->index],
- scaler->conf.kernel.name, plane_names[tex.type]);
+ scaler->conf.kernel.name, plane_names[img.type]);
bool is_separated = scaler->kernel && !scaler->kernel->polar;
// Set up the transformation+prelude and bind the texture, for everything
// other than separated scaling (which does this in the subfunction)
if (!is_separated)
- sampler_prelude(p->sc, pass_bind(p, tex));
+ sampler_prelude(p->sc, pass_bind(p, img));
// Dispatch the scaler. They're all wildly different.
const char *name = scaler->conf.kernel.name;
@@ -1741,9 +1781,9 @@ static void pass_sample(struct gl_video *p, struct img_tex tex,
} else if (strcmp(name, "oversample") == 0) {
pass_sample_oversample(p->sc, scaler, w, h);
} else if (scaler->kernel && scaler->kernel->polar) {
- pass_dispatch_sample_polar(p, scaler, tex, w, h);
+ pass_dispatch_sample_polar(p, scaler, img, w, h);
} else if (scaler->kernel) {
- pass_sample_separated(p, tex, scaler, w, h);
+ pass_sample_separated(p, img, scaler, w, h);
} else {
// Should never happen
abort();
@@ -1752,14 +1792,14 @@ static void pass_sample(struct gl_video *p, struct img_tex tex,
// Apply any required multipliers. Separated scaling already does this in
// its first stage
if (!is_separated)
- GLSLF("color *= %f;\n", tex.multiplier);
+ GLSLF("color *= %f;\n", img.multiplier);
// Micro-optimization: Avoid scaling unneeded channels
- skip_unused(p, tex.components);
+ skip_unused(p, img.components);
}
-// Returns true if two img_texs are semantically equivalent (same metadata)
-static bool img_tex_equiv(struct img_tex a, struct img_tex b)
+// Returns true if two images are semantically equivalent (same metadata)
+static bool image_equiv(struct image a, struct image b)
{
return a.type == b.type &&
a.components == b.components &&
@@ -1772,27 +1812,15 @@ static bool img_tex_equiv(struct img_tex a, struct img_tex b)
gl_transform_eq(a.transform, b.transform);
}
-static bool add_hook(struct gl_video *p, struct tex_hook hook)
-{
- if (p->tex_hook_num < SHADER_MAX_PASSES) {
- p->tex_hooks[p->tex_hook_num++] = hook;
- return true;
- } else {
- MP_ERR(p, "Too many passes! Limit is %d.\n", SHADER_MAX_PASSES);
- talloc_free(hook.priv);
- return false;
- }
-}
-
-static void deband_hook(struct gl_video *p, struct img_tex tex,
+static void deband_hook(struct gl_video *p, struct image img,
struct gl_transform *trans, void *priv)
{
- pass_describe(p, "debanding (%s)", plane_names[tex.type]);
+ pass_describe(p, "debanding (%s)", plane_names[img.type]);
pass_sample_deband(p->sc, p->opts.deband_opts, &p->lfg,
p->image_params.color.gamma);
}
-static void unsharp_hook(struct gl_video *p, struct img_tex tex,
+static void unsharp_hook(struct gl_video *p, struct image img,
struct gl_transform *trans, void *priv)
{
pass_describe(p, "unsharp masking");
@@ -1801,7 +1829,7 @@ static void unsharp_hook(struct gl_video *p, struct img_tex tex,
struct szexp_ctx {
struct gl_video *p;
- struct img_tex tex;
+ struct image img;
};
static bool szexp_lookup(void *priv, struct bstr var, float size[2])
@@ -1825,15 +1853,15 @@ static bool szexp_lookup(void *priv, struct bstr var, float size[2])
// HOOKED is a special case
if (bstr_equals0(var, "HOOKED")) {
- size[0] = ctx->tex.w;
- size[1] = ctx->tex.h;
+ size[0] = ctx->img.w;
+ size[1] = ctx->img.h;
return true;
}
- for (int o = 0; o < p->saved_tex_num; o++) {
- if (bstr_equals0(var, p->saved_tex[o].name)) {
- size[0] = p->saved_tex[o].tex.w;
- size[1] = p->saved_tex[o].tex.h;
+ for (int o = 0; o < p->num_saved_imgs; o++) {
+ if (bstr_equals0(var, p->saved_imgs[o].name)) {
+ size[0] = p->saved_imgs[o].img.w;
+ size[1] = p->saved_imgs[o].img.h;
return true;
}
}
@@ -1841,17 +1869,18 @@ static bool szexp_lookup(void *priv, struct bstr var, float size[2])
return false;
}
-static bool user_hook_cond(struct gl_video *p, struct img_tex tex, void *priv)
+static bool user_hook_cond(struct gl_video *p, struct image img, void *priv)
{
struct gl_user_shader_hook *shader = priv;
assert(shader);
float res = false;
- eval_szexpr(p->log, &(struct szexp_ctx){p, tex}, szexp_lookup, shader->cond, &res);
+ struct szexp_ctx ctx = {p, img};
+ eval_szexpr(p->log, &ctx, szexp_lookup, shader->cond, &res);
return res;
}
-static void user_hook(struct gl_video *p, struct img_tex tex,
+static void user_hook(struct gl_video *p, struct image img,
struct gl_transform *trans, void *priv)
{
struct gl_user_shader_hook *shader = priv;
@@ -1859,7 +1888,7 @@ static void user_hook(struct gl_video *p, struct img_tex tex,
load_shader(p, shader->pass_body);
pass_describe(p, "user shader: %.*s (%s)", BSTR_P(shader->pass_desc),
- plane_names[tex.type]);
+ plane_names[img.type]);
if (shader->compute.active) {
p->pass_compute = shader->compute;
@@ -1872,10 +1901,10 @@ static void user_hook(struct gl_video *p, struct img_tex tex,
// to do this and display an error message than just crash OpenGL
float w = 1.0, h = 1.0;
- eval_szexpr(p->log, &(struct szexp_ctx){p, tex}, szexp_lookup, shader->width, &w);
- eval_szexpr(p->log, &(struct szexp_ctx){p, tex}, szexp_lookup, shader->height, &h);
+ eval_szexpr(p->log, &(struct szexp_ctx){p, img}, szexp_lookup, shader->width, &w);
+ eval_szexpr(p->log, &(struct szexp_ctx){p, img}, szexp_lookup, shader->height, &h);
- *trans = (struct gl_transform){{{w / tex.w, 0}, {0, h / tex.h}}};
+ *trans = (struct gl_transform){{{w / img.w, 0}, {0, h / img.h}}};
gl_transform_trans(shader->offset, trans);
}
@@ -1898,27 +1927,22 @@ static bool add_user_hook(void *priv, struct gl_user_shader_hook hook)
for (int h = 0; h < SHADER_MAX_BINDS; h++)
texhook.bind_tex[h] = bstrdup0(copy, hook.bind_tex[h]);
- return add_hook(p, texhook);
+ MP_TARRAY_APPEND(p, p->tex_hooks, p->num_tex_hooks, texhook);
+ return true;
}
static bool add_user_tex(void *priv, struct gl_user_shader_tex tex)
{
struct gl_video *p = priv;
- if (p->user_tex_num == SHADER_MAX_PASSES) {
- MP_ERR(p, "Too many textures! Limit is %d.\n", SHADER_MAX_PASSES);
- goto err;
- }
-
tex.tex = ra_tex_create(p->ra, &tex.params);
TA_FREEP(&tex.params.initial_data);
- p->user_textures[p->user_tex_num++] = tex;
- return true;
+ if (!tex.tex)
+ return false;
-err:
- talloc_free(tex.params.initial_data);
- return false;
+ MP_TARRAY_APPEND(p, p->user_textures, p->num_user_textures, tex);
+ return true;
}
static void load_user_shaders(struct gl_video *p, char **shaders)
@@ -1937,7 +1961,7 @@ static void gl_video_setup_hooks(struct gl_video *p)
gl_video_reset_hooks(p);
if (p->opts.deband) {
- add_hook(p, (struct tex_hook) {
+ MP_TARRAY_APPEND(p, p->tex_hooks, p->num_tex_hooks, (struct tex_hook) {
.hook_tex = {"LUMA", "CHROMA", "RGB", "XYZ"},
.bind_tex = {"HOOKED"},
.hook = deband_hook,
@@ -1945,7 +1969,7 @@ static void gl_video_setup_hooks(struct gl_video *p)
}
if (p->opts.unsharp != 0.0) {
- add_hook(p, (struct tex_hook) {
+ MP_TARRAY_APPEND(p, p->tex_hooks, p->num_tex_hooks, (struct tex_hook) {
.hook_tex = {"MAIN"},
.bind_tex = {"HOOKED"},
.hook = unsharp_hook,
@@ -1958,55 +1982,55 @@ static void gl_video_setup_hooks(struct gl_video *p)
// sample from video textures, set "color" variable to yuv value
static void pass_read_video(struct gl_video *p)
{
- struct img_tex tex[4];
+ struct image img[4];
struct gl_transform offsets[4];
- pass_get_img_tex(p, &p->image, tex, offsets);
+ pass_get_images(p, &p->image, img, offsets);
// To keep the code as simple as possibly, we currently run all shader
// stages even if they would be unnecessary (e.g. no hooks for a texture).
- // In the future, deferred img_tex should optimize this away.
+ // In the future, deferred image should optimize this away.
// Merge semantically identical textures. This loop is done from back
// to front so that merged textures end up in the right order while
// simultaneously allowing us to skip unnecessary merges
for (int n = 3; n >= 0; n--) {
- if (tex[n].type == PLANE_NONE)
+ if (img[n].type == PLANE_NONE)
continue;
int first = n;
int num = 0;
for (int i = 0; i < n; i++) {
- if (img_tex_equiv(tex[n], tex[i]) &&
+ if (image_equiv(img[n], img[i]) &&
gl_transform_eq(offsets[n], offsets[i]))
{
GLSLF("// merging plane %d ...\n", i);
- copy_img_tex(p, &num, tex[i]);
+ copy_image(p, &num, img[i]);
first = MPMIN(first, i);
- tex[i] = (struct img_tex){0};
+ img[i] = (struct image){0};
}
}
if (num > 0) {
GLSLF("// merging plane %d ... into %d\n", n, first);
- copy_img_tex(p, &num, tex[n]);
+ copy_image(p, &num, img[n]);
pass_describe(p, "merging planes");
- finish_pass_fbo(p, &p->merge_fbo[n], tex[n].w, tex[n].h, 0);
- tex[first] = img_tex_fbo(&p->merge_fbo[n], tex[n].type, num);
- tex[n] = (struct img_tex){0};
+ finish_pass_tex(p, &p->merge_tex[n], img[n].w, img[n].h);
+ img[first] = image_wrap(p->merge_tex[n], img[n].type, num);
+ img[n] = (struct image){0};
}
}
// If any textures are still in integer format by this point, we need
// to introduce an explicit conversion pass to avoid breaking hooks/scaling
for (int n = 0; n < 4; n++) {
- if (tex[n].tex && tex[n].tex->params.format->ctype == RA_CTYPE_UINT) {
+ if (img[n].tex && img[n].tex->params.format->ctype == RA_CTYPE_UINT) {
GLSLF("// use_integer fix for plane %d\n", n);
- copy_img_tex(p, &(int){0}, tex[n]);
+ copy_image(p, &(int){0}, img[n]);
pass_describe(p, "use_integer fix");
- finish_pass_fbo(p, &p->integer_fbo[n], tex[n].w, tex[n].h, 0);
- tex[n] = img_tex_fbo(&p->integer_fbo[n], tex[n].type,
- tex[n].components);
+ finish_pass_tex(p, &p->integer_tex[n], img[n].w, img[n].h);
+ img[n] = image_wrap(p->integer_tex[n], img[n].type,
+ img[n].components);
}
}
@@ -2014,7 +2038,7 @@ static void pass_read_video(struct gl_video *p)
// modifying them in the process
for (int n = 0; n < 4; n++) {
const char *name;
- switch (tex[n].type) {
+ switch (img[n].type) {
case PLANE_RGB: name = "RGB"; break;
case PLANE_LUMA: name = "LUMA"; break;
case PLANE_CHROMA: name = "CHROMA"; break;
@@ -2023,7 +2047,7 @@ static void pass_read_video(struct gl_video *p)
default: continue;
}
- tex[n] = pass_hook(p, name, tex[n], &offsets[n]);
+ img[n] = pass_hook(p, name, img[n], &offsets[n]);
}
// At this point all planes are finalized but they may not be at the
@@ -2032,15 +2056,15 @@ static void pass_read_video(struct gl_video *p)
// the rgb/luma texture is the "reference" and scale everything else
// to match.
for (int n = 0; n < 4; n++) {
- switch (tex[n].type) {
+ switch (img[n].type) {
case PLANE_RGB:
case PLANE_XYZ:
case PLANE_LUMA: break;
default: continue;
}
- p->texture_w = tex[n].w;
- p->texture_h = tex[n].h;
+ p->texture_w = img[n].w;
+ p->texture_h = img[n].h;
p->texture_offset = offsets[n];
break;
}
@@ -2049,20 +2073,16 @@ static void pass_read_video(struct gl_video *p)
struct mp_rect_f src = {0.0, 0.0, p->image_params.w, p->image_params.h};
struct mp_rect_f ref = src;
gl_transform_rect(p->texture_offset, &ref);
- MP_DBG(p, "ref rect: {%f %f} {%f %f}\n", ref.x0, ref.y0, ref.x1, ref.y1);
// Explicitly scale all of the textures that don't match
for (int n = 0; n < 4; n++) {
- if (tex[n].type == PLANE_NONE)
+ if (img[n].type == PLANE_NONE)
continue;
// If the planes are aligned identically, we will end up with the
// exact same source rectangle.
struct mp_rect_f rect = src;
gl_transform_rect(offsets[n], &rect);
- MP_DBG(p, "rect[%d]: {%f %f} {%f %f}\n", n,
- rect.x0, rect.y0, rect.x1, rect.y1);
-
if (mp_rect_f_seq(ref, rect))
continue;
@@ -2074,23 +2094,19 @@ static void pass_read_video(struct gl_video *p)
{0.0, (ref.y1 - ref.y0) / (rect.y1 - rect.y0)}},
.t = {ref.x0, ref.y0},
};
- MP_DBG(p, "-> fix[%d] = {%f %f} + off {%f %f}\n", n,
- fix.m[0][0], fix.m[1][1], fix.t[0], fix.t[1]);
// Since the scale in texture space is different from the scale in
// absolute terms, we have to scale the coefficients down to be
// relative to the texture's physical dimensions and local offset
struct gl_transform scale = {
- .m = {{(float)tex[n].w / p->texture_w, 0.0},
- {0.0, (float)tex[n].h / p->texture_h}},
+ .m = {{(float)img[n].w / p->texture_w, 0.0},
+ {0.0, (float)img[n].h / p->texture_h}},
.t = {-rect.x0, -rect.y0},
};
if (p->image_params.rotate % 180 == 90)
MPSWAP(double, scale.m[0][0], scale.m[1][1]);
gl_transform_trans(scale, &fix);
- MP_DBG(p, "-> scaled[%d] = {%f %f} + off {%f %f}\n", n,
- fix.m[0][0], fix.m[1][1], fix.t[0], fix.t[1]);
// Since the texture transform is a function of the texture coordinates
// to texture space, rather than the other way around, we have to
@@ -2100,11 +2116,11 @@ static void pass_read_video(struct gl_video *p)
fix.m[1][1] = 1.0 / fix.m[1][1];
fix.t[0] = fix.m[0][0] * -fix.t[0];
fix.t[1] = fix.m[1][1] * -fix.t[1];
- gl_transform_trans(fix, &tex[n].transform);
+ gl_transform_trans(fix, &img[n].transform);
int scaler_id = -1;
const char *name = NULL;
- switch (tex[n].type) {
+ switch (img[n].type) {
case PLANE_RGB:
case PLANE_LUMA:
case PLANE_XYZ:
@@ -2129,31 +2145,31 @@ static void pass_read_video(struct gl_video *p)
// bilinear scaling is a free no-op thanks to GPU sampling
if (strcmp(conf->kernel.name, "bilinear") != 0) {
GLSLF("// upscaling plane %d\n", n);
- pass_sample(p, tex[n], scaler, conf, 1.0, p->texture_w, p->texture_h);
- finish_pass_fbo(p, &p->scale_fbo[n], p->texture_w, p->texture_h, 0);
- tex[n] = img_tex_fbo(&p->scale_fbo[n], tex[n].type, tex[n].components);
+ pass_sample(p, img[n], scaler, conf, 1.0, p->texture_w, p->texture_h);
+ finish_pass_tex(p, &p->scale_tex[n], p->texture_w, p->texture_h);
+ img[n] = image_wrap(p->scale_tex[n], img[n].type, img[n].components);
}
// Run any post-scaling hooks
- tex[n] = pass_hook(p, name, tex[n], NULL);
+ img[n] = pass_hook(p, name, img[n], NULL);
}
// All planes are of the same size and properly aligned at this point
- GLSLF("// combining planes\n");
+ pass_describe(p, "combining planes");
int coord = 0;
for (int i = 0; i < 4; i++) {
- if (tex[i].type != PLANE_NONE)
- copy_img_tex(p, &coord, tex[i]);
+ if (img[i].type != PLANE_NONE)
+ copy_image(p, &coord, img[i]);
}
p->components = coord;
}
-// Utility function that simply binds an FBO and reads from it, without any
+// Utility function that simply binds a texture and reads from it, without any
// transformations.
-static void pass_read_fbo(struct gl_video *p, struct fbotex *fbo)
+static void pass_read_tex(struct gl_video *p, struct ra_tex *tex)
{
- struct img_tex tex = img_tex_fbo(fbo, PLANE_RGB, p->components);
- copy_img_tex(p, &(int){0}, tex);
+ struct image img = image_wrap(tex, PLANE_RGB, p->components);
+ copy_image(p, &(int){0}, img);
}
// yuv conversion, and any other conversions before main up/down-scaling
@@ -2335,8 +2351,8 @@ static void pass_scale_main(struct gl_video *p)
compute_src_transform(p, &transform);
GLSLF("// main scaling\n");
- finish_pass_fbo(p, &p->indirect_fbo, p->texture_w, p->texture_h, 0);
- struct img_tex src = img_tex_fbo(&p->indirect_fbo, PLANE_RGB, p->components);
+ finish_pass_tex(p, &p->indirect_tex, p->texture_w, p->texture_h);
+ struct image src = image_wrap(p->indirect_tex, PLANE_RGB, p->components);
gl_transform_trans(transform, &src.transform);
pass_sample(p, src, scaler, &scaler_conf, scale_factor, vp_w, vp_h);
@@ -2571,6 +2587,7 @@ static void pass_dither(struct gl_video *p)
float matrix[2][2] = {{cos(r), -sin(r) },
{sin(r) * m, cos(r) * m}};
+ gl_sc_uniform_dynamic(p->sc);
gl_sc_uniform_mat2(p->sc, "dither_trafo", true, &matrix[0][0]);
GLSL(dither_pos = dither_trafo * dither_pos;)
@@ -2584,7 +2601,7 @@ static void pass_dither(struct gl_video *p)
// Draws the OSD, in scene-referred colors.. If cms is true, subtitles are
// instead adapted to the display's gamut.
static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
- struct mp_osd_res rect, struct fbodst target, bool cms)
+ struct mp_osd_res rect, struct ra_fbo fbo, bool cms)
{
mpgl_osd_generate(p->osd, rect, pts, p->image_params.stereo_out, draw_flags);
@@ -2604,7 +2621,7 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
pass_colormanage(p, csp_srgb, true);
}
- mpgl_osd_draw_finish(p->osd, n, p->sc, target);
+ mpgl_osd_draw_finish(p->osd, n, p->sc, fbo);
}
timer_pool_stop(p->osd_timer);
@@ -2620,17 +2637,17 @@ static float chroma_realign(int size, int pixel)
// Minimal rendering code path, for GLES or OpenGL 2.1 without proper FBOs.
static void pass_render_frame_dumb(struct gl_video *p)
{
- struct img_tex tex[4];
+ struct image img[4];
struct gl_transform off[4];
- pass_get_img_tex(p, &p->image, tex, off);
+ pass_get_images(p, &p->image, img, off);
struct gl_transform transform;
compute_src_transform(p, &transform);
int index = 0;
for (int i = 0; i < p->plane_count; i++) {
- int cw = tex[i].type == PLANE_CHROMA ? p->ra_format.chroma_w : 1;
- int ch = tex[i].type == PLANE_CHROMA ? p->ra_format.chroma_h : 1;
+ int cw = img[i].type == PLANE_CHROMA ? p->ra_format.chroma_w : 1;
+ int ch = img[i].type == PLANE_CHROMA ? p->ra_format.chroma_h : 1;
if (p->image_params.rotate % 180 == 90)
MPSWAP(int, cw, ch);
@@ -2644,10 +2661,10 @@ static void pass_render_frame_dumb(struct gl_video *p)
t.t[0] += off[i].t[0];
t.t[1] += off[i].t[1];
- gl_transform_trans(tex[i].transform, &t);
- tex[i].transform = t;
+ gl_transform_trans(img[i].transform, &t);
+ img[i].transform = t;
- copy_img_tex(p, &index, tex[i]);
+ copy_image(p, &index, img[i]);
}
pass_convert_yuv(p);
@@ -2662,8 +2679,8 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, uint64_t
p->texture_h = p->image_params.h;
p->texture_offset = identity_trans;
p->components = 0;
- p->saved_tex_num = 0;
- p->hook_fbo_num = 0;
+ p->num_saved_imgs = 0;
+ p->idx_hook_textures = 0;
p->use_linear = false;
// try uploading the frame
@@ -2693,10 +2710,10 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, uint64_t
.w = p->texture_w, .h = p->texture_h,
.display_par = scale[1] / scale[0], // counter compensate scaling
};
- finish_pass_fbo(p, &p->blend_subs_fbo, rect.w, rect.h, 0);
- pass_draw_osd(p, OSD_DRAW_SUB_ONLY, vpts, rect,
- p->blend_subs_fbo.fbo, false);
- pass_read_fbo(p, &p->blend_subs_fbo);
+ finish_pass_tex(p, &p->blend_subs_tex, rect.w, rect.h);
+ struct ra_fbo fbo = { p->blend_subs_tex };
+ pass_draw_osd(p, OSD_DRAW_SUB_ONLY, vpts, rect, fbo, false);
+ pass_read_tex(p, p->blend_subs_tex);
pass_describe(p, "blend subs video");
}
pass_opt_hook_point(p, "MAIN", &p->texture_offset);
@@ -2723,10 +2740,10 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, uint64_t
pass_delinearize(p->sc, p->image_params.color.gamma);
p->use_linear = false;
}
- finish_pass_fbo(p, &p->blend_subs_fbo, p->texture_w, p->texture_h, 0);
- pass_draw_osd(p, OSD_DRAW_SUB_ONLY, vpts, rect,
- p->blend_subs_fbo.fbo, false);
- pass_read_fbo(p, &p->blend_subs_fbo);
+ finish_pass_tex(p, &p->blend_subs_tex, p->texture_w, p->texture_h);
+ struct ra_fbo fbo = { p->blend_subs_tex };
+ pass_draw_osd(p, OSD_DRAW_SUB_ONLY, vpts, rect, fbo, false);
+ pass_read_tex(p, p->blend_subs_tex);
pass_describe(p, "blend subs");
}
@@ -2735,7 +2752,7 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, uint64_t
return true;
}
-static void pass_draw_to_screen(struct gl_video *p, struct fbodst fbo)
+static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo)
{
if (p->dumb_mode)
pass_render_frame_dumb(p);
@@ -2749,15 +2766,15 @@ static void pass_draw_to_screen(struct gl_video *p, struct fbodst fbo)
pass_colormanage(p, p->image_params.color, false);
- // Since finish_pass_direct doesn't work with compute shaders, and neither
+ // Since finish_pass_fbo doesn't work with compute shaders, and neither
// does the checkerboard/dither code, we may need an indirection via
- // p->screen_fbo here.
+ // p->screen_tex here.
if (p->pass_compute.active) {
int o_w = p->dst_rect.x1 - p->dst_rect.x0,
o_h = p->dst_rect.y1 - p->dst_rect.y0;
- finish_pass_fbo(p, &p->screen_fbo, o_w, o_h, FBOTEX_FUZZY);
- struct img_tex tmp = img_tex_fbo(&p->screen_fbo, PLANE_RGB, p->components);
- copy_img_tex(p, &(int){0}, tmp);
+ finish_pass_tex(p, &p->screen_tex, o_w, o_h);
+ struct image tmp = image_wrap(p->screen_tex, PLANE_RGB, p->components);
+ copy_image(p, &(int){0}, tmp);
}
if (p->has_alpha){
@@ -2765,14 +2782,16 @@ static void pass_draw_to_screen(struct gl_video *p, struct fbodst fbo)
// Draw checkerboard pattern to indicate transparency
GLSLF("// transparency checkerboard\n");
GLSL(bvec2 tile = lessThan(fract(gl_FragCoord.xy * 1.0/32.0), vec2(0.5));)
- GLSL(vec3 background = vec3(tile.x == tile.y ? 1.0 : 0.75);)
- GLSL(color.rgb = mix(background, color.rgb, color.a);)
+ GLSL(vec3 background = vec3(tile.x == tile.y ? 0.93 : 0.87);)
+ GLSL(color.rgb += background.rgb * (1.0 - color.a);)
+ GLSL(color.a = 1.0;)
} else if (p->opts.alpha_mode == ALPHA_BLEND) {
// Blend into background color (usually black)
struct m_color c = p->opts.background;
GLSLF("vec4 background = vec4(%f, %f, %f, %f);\n",
c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0);
- GLSL(color = mix(background, vec4(color.rgb, 1.0), color.a);)
+ GLSL(color.rgb += background.rgb * (1.0 - color.a);)
+ GLSL(color.a = background.a;)
}
}
@@ -2780,11 +2799,11 @@ static void pass_draw_to_screen(struct gl_video *p, struct fbodst fbo)
pass_dither(p);
pass_describe(p, "output to screen");
- finish_pass_direct(p, fbo, &p->dst_rect);
+ finish_pass_fbo(p, fbo, &p->dst_rect);
}
-static bool update_fbosurface(struct gl_video *p, struct mp_image *mpi,
- uint64_t id, struct fbosurface *surf)
+static bool update_surface(struct gl_video *p, struct mp_image *mpi,
+ uint64_t id, struct surface *surf)
{
int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
vp_h = p->dst_rect.y1 - p->dst_rect.y0;
@@ -2801,7 +2820,7 @@ static bool update_fbosurface(struct gl_video *p, struct mp_image *mpi,
pass_linearize(p->sc, p->image_params.color.gamma);
}
- finish_pass_fbo(p, &surf->fbotex, vp_w, vp_h, FBOTEX_FUZZY);
+ finish_pass_tex(p, &surf->tex, vp_w, vp_h);
surf->id = id;
surf->pts = mpi->pts;
return true;
@@ -2809,7 +2828,7 @@ static bool update_fbosurface(struct gl_video *p, struct mp_image *mpi,
// Draws an interpolate frame to fbo, based on the frame timing in t
static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
- struct fbodst fbo)
+ struct ra_fbo fbo)
{
bool is_new = false;
@@ -2822,8 +2841,8 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// First of all, figure out if we have a frame available at all, and draw
// it manually + reset the queue if not
if (p->surfaces[p->surface_now].id == 0) {
- struct fbosurface *now = &p->surfaces[p->surface_now];
- if (!update_fbosurface(p, t->current, t->frame_id, now))
+ struct surface *now = &p->surfaces[p->surface_now];
+ if (!update_surface(p, t->current, t->frame_id, now))
return;
p->surface_idx = p->surface_now;
is_new = true;
@@ -2831,13 +2850,13 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// Find the right frame for this instant
if (t->current) {
- int next = fbosurface_wrap(p->surface_now + 1);
+ int next = surface_wrap(p->surface_now + 1);
while (p->surfaces[next].id &&
p->surfaces[next].id > p->surfaces[p->surface_now].id &&
p->surfaces[p->surface_now].id < t->frame_id)
{
p->surface_now = next;
- next = fbosurface_wrap(next + 1);
+ next = surface_wrap(next + 1);
}
}
@@ -2856,20 +2875,19 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
} else {
assert(tscale->kernel && !tscale->kernel->polar);
size = ceil(tscale->kernel->size);
- assert(size <= TEXUNIT_VIDEO_NUM);
}
int radius = size/2;
int surface_now = p->surface_now;
- int surface_bse = fbosurface_wrap(surface_now - (radius-1));
- int surface_end = fbosurface_wrap(surface_now + radius);
- assert(fbosurface_wrap(surface_bse + size-1) == surface_end);
+ int surface_bse = surface_wrap(surface_now - (radius-1));
+ int surface_end = surface_wrap(surface_now + radius);
+ assert(surface_wrap(surface_bse + size-1) == surface_end);
// Render new frames while there's room in the queue. Note that technically,
// this should be done before the step where we find the right frame, but
// it only barely matters at the very beginning of playback, and this way
// makes the code much more linear.
- int surface_dst = fbosurface_wrap(p->surface_idx + 1);
+ int surface_dst = surface_wrap(p->surface_idx + 1);
for (int i = 0; i < t->num_frames; i++) {
// Avoid overwriting data we might still need
if (surface_dst == surface_bse - 1)
@@ -2881,11 +2899,11 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
continue;
if (f_id > p->surfaces[p->surface_idx].id) {
- struct fbosurface *dst = &p->surfaces[surface_dst];
- if (!update_fbosurface(p, f, f_id, dst))
+ struct surface *dst = &p->surfaces[surface_dst];
+ if (!update_surface(p, f, f_id, dst))
return;
p->surface_idx = surface_dst;
- surface_dst = fbosurface_wrap(surface_dst + 1);
+ surface_dst = surface_wrap(surface_dst + 1);
is_new = true;
}
}
@@ -2897,7 +2915,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// end of playback or start of playback.
bool valid = true;
for (int i = surface_bse, ii; valid && i != surface_end; i = ii) {
- ii = fbosurface_wrap(i + 1);
+ ii = surface_wrap(i + 1);
if (p->surfaces[i].id == 0 || p->surfaces[ii].id == 0) {
valid = false;
} else if (p->surfaces[ii].id < p->surfaces[i].id) {
@@ -2915,7 +2933,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
pass_describe(p, "interpolation");
if (!valid || t->still) {
// surface_now is guaranteed to be valid, so we can safely use it.
- pass_read_fbo(p, &p->surfaces[surface_now].fbotex);
+ pass_read_tex(p, p->surfaces[surface_now].tex);
p->is_interpolated = false;
} else {
double mix = t->vsync_offset / t->ideal_frame_duration;
@@ -2923,7 +2941,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// so we try to adjust by using the previous set of N frames instead
// (which requires some extra checking to make sure it's valid)
if (mix < 0.0) {
- int prev = fbosurface_wrap(surface_bse - 1);
+ int prev = surface_wrap(surface_bse - 1);
if (p->surfaces[prev].id != 0 &&
p->surfaces[prev].id < p->surfaces[surface_bse].id)
{
@@ -2949,20 +2967,22 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// Blend the frames together
if (oversample || linear) {
+ gl_sc_uniform_dynamic(p->sc);
gl_sc_uniform_f(p->sc, "inter_coeff", mix);
GLSL(color = mix(texture(texture0, texcoord0),
texture(texture1, texcoord1),
inter_coeff);)
} else {
+ gl_sc_uniform_dynamic(p->sc);
gl_sc_uniform_f(p->sc, "fcoord", mix);
pass_sample_separated_gen(p->sc, tscale, 0, 0);
}
// Load all the required frames
for (int i = 0; i < size; i++) {
- struct img_tex img =
- img_tex_fbo(&p->surfaces[fbosurface_wrap(surface_bse+i)].fbotex,
- PLANE_RGB, p->components);
+ struct image img =
+ image_wrap(p->surfaces[surface_wrap(surface_bse+i)].tex,
+ PLANE_RGB, p->components);
// Since the code in pass_sample_separated currently assumes
// the textures are bound in-order and starting at 0, we just
// assert to make sure this is the case (which it should always be)
@@ -2970,8 +2990,8 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
assert(id == i);
}
- MP_DBG(p, "inter frame dur: %f vsync: %f, mix: %f\n",
- t->ideal_frame_duration, t->vsync_interval, mix);
+ MP_TRACE(p, "inter frame dur: %f vsync: %f, mix: %f\n",
+ t->ideal_frame_duration, t->vsync_interval, mix);
p->is_interpolated = true;
}
pass_draw_to_screen(p, fbo);
@@ -2980,9 +3000,11 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
}
void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame,
- struct fbodst target)
+ struct ra_fbo fbo)
{
- struct mp_rect target_rc = {0, 0, target.tex->params.w, target.tex->params.h};
+ gl_video_update_options(p);
+
+ struct mp_rect target_rc = {0, 0, fbo.tex->params.w, fbo.tex->params.h};
p->broken_frame = false;
@@ -2991,18 +3013,18 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame,
if (!has_frame || !mp_rect_equals(&p->dst_rect, &target_rc)) {
struct m_color c = p->clear_color;
float color[4] = {c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0};
- p->ra->fns->clear(p->ra, target.tex, color, &target_rc);
+ p->ra->fns->clear(p->ra, fbo.tex, color, &target_rc);
}
- if (p->hwdec_active && p->hwdec->driver->overlay_frame) {
+ if (p->hwdec_overlay) {
if (has_frame) {
- float *color = p->hwdec->overlay_colorkey;
- p->ra->fns->clear(p->ra, target.tex, color, &p->dst_rect);
+ float *color = p->hwdec_overlay->overlay_colorkey;
+ p->ra->fns->clear(p->ra, fbo.tex, color, &p->dst_rect);
}
- p->hwdec->driver->overlay_frame(p->hwdec, frame->current,
- &p->src_rect, &p->dst_rect,
- frame->frame_id != p->image.id);
+ p->hwdec_overlay->driver->overlay_frame(p->hwdec_overlay, frame->current,
+ &p->src_rect, &p->dst_rect,
+ frame->frame_id != p->image.id);
if (frame->current)
p->osd_pts = frame->current->pts;
@@ -3021,7 +3043,7 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame,
}
if (interpolate) {
- gl_video_interpolate_frame(p, frame, target);
+ gl_video_interpolate_frame(p, frame, fbo);
} else {
bool is_new = frame->frame_id != p->image.id;
@@ -3029,41 +3051,42 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame,
if (frame->still && p->opts.blend_subs)
is_new = true;
- if (is_new || !p->output_fbo_valid) {
- p->output_fbo_valid = false;
+ if (is_new || !p->output_tex_valid) {
+ p->output_tex_valid = false;
pass_info_reset(p, !is_new);
if (!pass_render_frame(p, frame->current, frame->frame_id))
goto done;
// For the non-interpolation case, we draw to a single "cache"
- // FBO to speed up subsequent re-draws (if any exist)
- struct fbodst dest_fbo = target;
+ // texture to speed up subsequent re-draws (if any exist)
+ struct ra_fbo dest_fbo = fbo;
if (frame->num_vsyncs > 1 && frame->display_synced &&
!p->dumb_mode && (p->ra->caps & RA_CAP_BLIT))
{
- fbotex_change(&p->output_fbo, p->ra, p->log,
- target.tex->params.w, target.tex->params.h,
- p->fbo_format, FBOTEX_FUZZY);
- dest_fbo = p->output_fbo.fbo;
- p->output_fbo_valid = true;
+ bool r = ra_tex_resize(p->ra, p->log, &p->output_tex,
+ fbo.tex->params.w, fbo.tex->params.h,
+ p->fbo_format);
+ if (r) {
+ dest_fbo = (struct ra_fbo) { p->output_tex };
+ p->output_tex_valid = true;
+ }
}
pass_draw_to_screen(p, dest_fbo);
}
- // "output fbo valid" and "output fbo needed" are equivalent
- if (p->output_fbo_valid) {
+ // "output tex valid" and "output tex needed" are equivalent
+ if (p->output_tex_valid) {
pass_info_reset(p, true);
pass_describe(p, "redraw cached frame");
struct mp_rect src = p->dst_rect;
struct mp_rect dst = src;
- if (target.flip) {
- dst.y0 = target.tex->params.h - src.y0;
- dst.y1 = target.tex->params.h - src.y1;
+ if (fbo.flip) {
+ dst.y0 = fbo.tex->params.h - src.y0;
+ dst.y1 = fbo.tex->params.h - src.y1;
}
timer_pool_start(p->blit_timer);
- p->ra->fns->blit(p->ra, target.tex, p->output_fbo.tex,
- &dst, &src);
+ p->ra->fns->blit(p->ra, fbo.tex, p->output_tex, &dst, &src);
timer_pool_stop(p->blit_timer);
pass_record(p, timer_pool_measure(p->blit_timer));
}
@@ -3072,8 +3095,6 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame,
done:
- unmap_current_image(p);
-
debug_check_gl(p, "after video rendering");
if (p->osd) {
@@ -3084,7 +3105,7 @@ done:
pass_info_reset(p, true);
pass_draw_osd(p, p->opts.blend_subs ? OSD_DRAW_OSD_ONLY : 0,
- p->osd_pts, p->osd_rect, target, true);
+ p->osd_pts, p->osd_rect, fbo, true);
debug_check_gl(p, "after OSD rendering");
}
@@ -3092,17 +3113,7 @@ done:
// Make the screen solid blue to make it visually clear that an
// error has occurred
float color[4] = {0.0, 0.05, 0.5, 1.0};
- p->ra->fns->clear(p->ra, target.tex, color, &target_rc);
- }
-
- // The playloop calls this last before waiting some time until it decides
- // to call flip_page(). Tell OpenGL to start execution of the GPU commands
- // while we sleep (this happens asynchronously).
- if ((p->opts.early_flush == -1 && !frame->display_synced) ||
- p->opts.early_flush == 1)
- {
- if (p->ra->fns->flush)
- p->ra->fns->flush(p->ra);
+ p->ra->fns->clear(p->ra, fbo.tex, color, &target_rc);
}
p->frames_rendered++;
@@ -3148,7 +3159,7 @@ void gl_video_resize(struct gl_video *p,
static void frame_perf_data(struct pass_info pass[], struct mp_frame_perf *out)
{
- for (int i = 0; i < PASS_INFO_MAX; i++) {
+ for (int i = 0; i < VO_PASS_PERF_MAX; i++) {
if (!pass[i].desc.len)
break;
out->perf[out->count] = pass[i].perf;
@@ -3169,14 +3180,14 @@ static void reinterleave_vdpau(struct gl_video *p,
struct ra_tex *input[4], struct ra_tex *output[2])
{
for (int n = 0; n < 2; n++) {
- struct fbotex *fbo = &p->vdpau_deinterleave_fbo[n];
+ struct ra_tex **tex = &p->vdpau_deinterleave_tex[n];
// This is an array of the 2 to-merge planes.
struct ra_tex **src = &input[n * 2];
int w = src[0]->params.w;
int h = src[0]->params.h;
int ids[2];
for (int t = 0; t < 2; t++) {
- ids[t] = pass_bind(p, (struct img_tex){
+ ids[t] = pass_bind(p, (struct image){
.tex = src[t],
.multiplier = 1.0,
.transform = identity_trans,
@@ -3185,18 +3196,18 @@ static void reinterleave_vdpau(struct gl_video *p,
});
}
+ pass_describe(p, "vdpau reinterleaving");
GLSLF("color = fract(gl_FragCoord.y * 0.5) < 0.5\n");
GLSLF(" ? texture(texture%d, texcoord%d)\n", ids[0], ids[0]);
GLSLF(" : texture(texture%d, texcoord%d);", ids[1], ids[1]);
- const struct ra_format *fmt =
- ra_find_unorm_format(p->ra, 1, n == 0 ? 1 : 2);
- fbotex_change(fbo, p->ra, p->log, w, h * 2, fmt, 0);
-
- pass_describe(p, "vdpau reinterleaving");
- finish_pass_direct(p, fbo->fbo, &(struct mp_rect){0, 0, w, h * 2});
+ int comps = n == 0 ? 1 : 2;
+ const struct ra_format *fmt = ra_find_unorm_format(p->ra, 1, comps);
+ ra_tex_resize(p->ra, p->log, tex, w, h * 2, fmt);
+ struct ra_fbo fbo = { *tex };
+ finish_pass_fbo(p, fbo, &(struct mp_rect){0, 0, w, h * 2});
- output[n] = fbo->tex;
+ output[n] = *tex;
}
}
@@ -3262,8 +3273,6 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &vimg->planes[n];
- plane->flipped = mpi->stride[0] < 0;
-
struct ra_tex_upload_params params = {
.tex = plane->tex,
.src = mpi->planes[n],
@@ -3271,6 +3280,13 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t
.stride = mpi->stride[n],
};
+ plane->flipped = params.stride < 0;
+ if (plane->flipped) {
+ int h = mp_image_plane_h(mpi, n);
+ params.src = (char *)params.src + (h - 1) * params.stride;
+ params.stride = -params.stride;
+ }
+
struct dr_buffer *mapped = gl_find_dr_buffer(p, mpi->planes[n]);
if (mapped) {
params.buf = mapped->buf;
@@ -3310,9 +3326,9 @@ error:
static bool test_fbo(struct gl_video *p, const struct ra_format *fmt)
{
MP_VERBOSE(p, "Testing FBO format %s\n", fmt->name);
- struct fbotex fbo = {0};
- bool success = fbotex_change(&fbo, p->ra, p->log, 16, 16, fmt, 0);
- fbotex_uninit(&fbo);
+ struct ra_tex *tex = NULL;
+ bool success = ra_tex_resize(p->ra, p->log, &tex, 16, 16, fmt);
+ ra_tex_free(p->ra, &tex);
return success;
}
@@ -3359,7 +3375,8 @@ static void check_gl_features(struct gl_video *p)
bool have_compute = ra->caps & RA_CAP_COMPUTE;
bool have_ssbo = ra->caps & RA_CAP_BUF_RW;
- const char *auto_fbo_fmts[] = {"rgba16", "rgba16f", "rgb10_a2", "rgba8", 0};
+ const char *auto_fbo_fmts[] = {"rgba16", "rgba16f", "rgba16hf",
+ "rgb10_a2", "rgba8", 0};
const char *user_fbo_fmts[] = {p->opts.fbo_format, 0};
const char **fbo_fmts = user_fbo_fmts[0] && strcmp(user_fbo_fmts[0], "auto")
? user_fbo_fmts : auto_fbo_fmts;
@@ -3388,7 +3405,6 @@ static void check_gl_features(struct gl_video *p)
"Most extended features will be disabled.\n");
}
p->dumb_mode = true;
- p->use_lut_3d = false;
// Most things don't work, so whitelist all options that still work.
p->opts = (struct gl_video_opts){
.gamma = p->opts.gamma,
@@ -3409,9 +3425,13 @@ static void check_gl_features(struct gl_video *p)
.tone_mapping_param = p->opts.tone_mapping_param,
.tone_mapping_desat = p->opts.tone_mapping_desat,
.early_flush = p->opts.early_flush,
+ .icc_opts = p->opts.icc_opts,
+ .hwdec_interop = p->opts.hwdec_interop,
};
for (int n = 0; n < SCALER_COUNT; n++)
p->opts.scaler[n] = gl_video_opts_def.scaler[n];
+ if (!have_fbo)
+ p->use_lut_3d = false;
return;
}
p->dumb_mode = false;
@@ -3463,6 +3483,19 @@ static void check_gl_features(struct gl_video *p)
p->opts.compute_hdr_peak = 0;
MP_WARN(p, "Disabling HDR peak computation (no compute shaders).\n");
}
+ if (!(ra->caps & RA_CAP_FRAGCOORD) && p->opts.dither_depth >= 0 &&
+ p->opts.dither_algo != DITHER_NONE)
+ {
+ p->opts.dither_algo = DITHER_NONE;
+ MP_WARN(p, "Disabling dithering (no gl_FragCoord).\n");
+ }
+ if (!(ra->caps & RA_CAP_FRAGCOORD) &&
+ p->opts.alpha_mode == ALPHA_BLEND_TILES)
+ {
+ p->opts.alpha_mode = ALPHA_BLEND;
+ // Verbose, since this is the default setting
+ MP_VERBOSE(p, "Disabling alpha checkerboard (no gl_FragCoord).\n");
+ }
}
static void init_gl(struct gl_video *p)
@@ -3486,6 +3519,10 @@ void gl_video_uninit(struct gl_video *p)
uninit_video(p);
+ for (int n = 0; n < p->num_hwdecs; n++)
+ ra_hwdec_uninit(p->hwdecs[n]);
+ p->num_hwdecs = 0;
+
gl_sc_destroy(p->sc);
ra_tex_free(p->ra, &p->lut_3d_texture);
@@ -3495,7 +3532,7 @@ void gl_video_uninit(struct gl_video *p)
timer_pool_destroy(p->blit_timer);
timer_pool_destroy(p->osd_timer);
- for (int i = 0; i < PASS_INFO_MAX; i++) {
+ for (int i = 0; i < VO_PASS_PERF_MAX; i++) {
talloc_free(p->pass_fresh[i].desc.start);
talloc_free(p->pass_redraw[i].desc.start);
}
@@ -3540,8 +3577,10 @@ bool gl_video_check_format(struct gl_video *p, int mp_format)
if (ra_get_imgfmt_desc(p->ra, mp_format, &desc) &&
is_imgfmt_desc_supported(p, &desc))
return true;
- if (p->hwdec && ra_hwdec_test_format(p->hwdec, mp_format))
- return true;
+ for (int n = 0; n < p->num_hwdecs; n++) {
+ if (ra_hwdec_test_format(p->hwdecs[n], mp_format))
+ return true;
+ }
return false;
}
@@ -3588,6 +3627,14 @@ struct gl_video *gl_video_init(struct ra *ra, struct mp_log *log,
p->opts = *opts;
for (int n = 0; n < SCALER_COUNT; n++)
p->scaler[n] = (struct scaler){.index = n};
+ // our VAO always has the vec2 position as the first element
+ MP_TARRAY_APPEND(p, p->vao, p->vao_len, (struct ra_renderpass_input) {
+ .name = "position",
+ .type = RA_VARTYPE_FLOAT,
+ .dim_v = 2,
+ .dim_m = 1,
+ .offset = 0,
+ });
init_gl(p);
reinit_from_options(p);
return p;
@@ -3612,12 +3659,15 @@ static const char *handle_scaler_opt(const char *name, bool tscale)
return NULL;
}
-void gl_video_update_options(struct gl_video *p)
+static void gl_video_update_options(struct gl_video *p)
{
if (m_config_cache_update(p->opts_cache)) {
gl_lcms_update_options(p->cms);
reinit_from_options(p);
}
+
+ if (mp_csp_equalizer_state_changed(p->video_eq))
+ p->output_tex_valid = false;
}
static void reinit_from_options(struct gl_video *p)
@@ -3648,6 +3698,8 @@ static void reinit_from_options(struct gl_video *p)
void gl_video_configure_queue(struct gl_video *p, struct vo *vo)
{
+ gl_video_update_options(p);
+
int queue_size = 1;
// Figure out an adequate size for the interpolation queue. The larger
@@ -3742,19 +3794,12 @@ float gl_video_scale_ambient_lux(float lmin, float lmax,
void gl_video_set_ambient_lux(struct gl_video *p, int lux)
{
if (p->opts.gamma_auto) {
- float gamma = gl_video_scale_ambient_lux(16.0, 64.0, 2.40, 1.961, lux);
- MP_VERBOSE(p, "ambient light changed: %dlux (gamma: %f)\n", lux, gamma);
- p->opts.gamma = MPMIN(1.0, 1.961 / gamma);
+ p->opts.gamma = gl_video_scale_ambient_lux(16.0, 256.0, 1.0, 1.2, lux);
+ MP_TRACE(p, "ambient light changed: %d lux (gamma: %f)\n", lux,
+ p->opts.gamma);
}
}
-void gl_video_set_hwdec(struct gl_video *p, struct ra_hwdec *hwdec)
-{
- unref_current_image(p);
- ra_hwdec_mapper_free(&p->hwdec_mapper);
- p->hwdec = hwdec;
-}
-
static void *gl_video_dr_alloc_buffer(struct gl_video *p, size_t size)
{
struct ra_buf_params params = {
@@ -3811,3 +3856,46 @@ struct mp_image *gl_video_get_image(struct gl_video *p, int imgfmt, int w, int h
gl_video_dr_free_buffer(p, ptr);
return res;
}
+
+static void load_add_hwdec(struct gl_video *p, struct mp_hwdec_devices *devs,
+ const struct ra_hwdec_driver *drv, bool is_auto)
+{
+ struct ra_hwdec *hwdec =
+ ra_hwdec_load_driver(p->ra, p->log, p->global, devs, drv, is_auto);
+ if (hwdec)
+ MP_TARRAY_APPEND(p, p->hwdecs, p->num_hwdecs, hwdec);
+}
+
+void gl_video_load_hwdecs(struct gl_video *p, struct mp_hwdec_devices *devs,
+ bool load_all_by_default)
+{
+ char *type = p->opts.hwdec_interop;
+ if (!type || !type[0] || strcmp(type, "auto") == 0) {
+ if (!load_all_by_default)
+ return;
+ type = "all";
+ }
+ if (strcmp(type, "no") == 0) {
+ // do nothing, just block further loading
+ } else if (strcmp(type, "all") == 0) {
+ gl_video_load_hwdecs_all(p, devs);
+ } else {
+ for (int n = 0; ra_hwdec_drivers[n]; n++) {
+ const struct ra_hwdec_driver *drv = ra_hwdec_drivers[n];
+ if (strcmp(type, drv->name) == 0) {
+ load_add_hwdec(p, devs, drv, false);
+ break;
+ }
+ }
+ }
+ p->hwdec_interop_loading_done = true;
+}
+
+void gl_video_load_hwdecs_all(struct gl_video *p, struct mp_hwdec_devices *devs)
+{
+ if (!p->hwdec_interop_loading_done) {
+ for (int n = 0; ra_hwdec_drivers[n]; n++)
+ load_add_hwdec(p, devs, ra_hwdec_drivers[n], true);
+ p->hwdec_interop_loading_done = true;
+ }
+}
diff --git a/video/out/opengl/video.h b/video/out/gpu/video.h
index d163bc8..78f8828 100644
--- a/video/out/opengl/video.h
+++ b/video/out/gpu/video.h
@@ -27,11 +27,6 @@
#include "shader_cache.h"
#include "video/csputils.h"
#include "video/out/filter_kernels.h"
-#include "video/out/vo.h"
-
-// Assume we have this many texture units for sourcing additional passes.
-// The actual texture unit assignment is dynamic.
-#define TEXUNIT_VIDEO_NUM 6
struct scaler_fun {
char *name;
@@ -56,7 +51,7 @@ struct scaler {
bool initialized;
struct filter_kernel *kernel;
struct ra_tex *lut;
- struct fbotex sep_fbo;
+ struct ra_tex *sep_fbo;
bool insufficient;
int lut_size;
@@ -144,6 +139,7 @@ struct gl_video_opts {
struct mp_icc_opts *icc_opts;
int early_flush;
char *shader_cache_dir;
+ char *hwdec_interop;
};
extern const struct m_sub_options gl_video_conf;
@@ -155,12 +151,11 @@ struct gl_video *gl_video_init(struct ra *ra, struct mp_log *log,
struct mpv_global *g);
void gl_video_uninit(struct gl_video *p);
void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd);
-void gl_video_update_options(struct gl_video *p);
bool gl_video_check_format(struct gl_video *p, int mp_format);
void gl_video_config(struct gl_video *p, struct mp_image_params *params);
void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b);
void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame,
- struct fbodst target);
+ struct ra_fbo fbo);
void gl_video_resize(struct gl_video *p,
struct mp_rect *src, struct mp_rect *dst,
struct mp_osd_res *osd);
@@ -182,8 +177,10 @@ struct mp_colorspace gl_video_get_output_colorspace(struct gl_video *p);
void gl_video_reset(struct gl_video *p);
bool gl_video_showing_interpolated_frame(struct gl_video *p);
-struct ra_hwdec;
-void gl_video_set_hwdec(struct gl_video *p, struct ra_hwdec *hwdec);
+struct mp_hwdec_devices;
+void gl_video_load_hwdecs(struct gl_video *p, struct mp_hwdec_devices *devs,
+ bool load_all_by_default);
+void gl_video_load_hwdecs_all(struct gl_video *p, struct mp_hwdec_devices *devs);
struct vo;
void gl_video_configure_queue(struct gl_video *p, struct vo *vo);
diff --git a/video/out/opengl/video_shaders.c b/video/out/gpu/video_shaders.c
index 60c5ce8..3e71c31 100644
--- a/video/out/opengl/video_shaders.c
+++ b/video/out/gpu/video_shaders.c
@@ -97,11 +97,11 @@ void pass_sample_separated_gen(struct gl_shader_cache *sc, struct scaler *scaler
}
// Subroutine for computing and adding an individual texel contribution
-// If subtexel < 0 and offset < 0, samples directly.
-// If subtexel >= 0, takes the texel from cN[subtexel]
-// If offset >= 0, takes the texel from inN[rel.y+y+offset][rel.x+x+offset]
+// If planar is false, samples directly
+// If planar is true, takes the pixel from inX[idx] where X is the component and
+// `idx` must be defined by the caller
static void polar_sample(struct gl_shader_cache *sc, struct scaler *scaler,
- int x, int y, int subtexel, int offset, int components)
+ int x, int y, int components, bool planar)
{
double radius = scaler->kernel->f.radius * scaler->kernel->filter_scale;
double radius_cutoff = scaler->kernel->radius_cutoff;
@@ -130,19 +130,12 @@ static void polar_sample(struct gl_shader_cache *sc, struct scaler *scaler,
}
GLSL(wsum += w;)
- if (subtexel < 0 && offset < 0) {
- GLSLF("c0 = texture(tex, base + pt * vec2(%d.0, %d.0));\n", x, y);
- GLSL(color += vec4(w) * c0;)
- } else if (subtexel >= 0) {
+ if (planar) {
for (int n = 0; n < components; n++)
- GLSLF("color[%d] += w * c%d[%d];\n", n, n, subtexel);
- } else if (offset >= 0) {
- for (int n = 0; n <components; n++)
- GLSLF("color[%d] += w * in%d[rel.y+%d][rel.x+%d];\n", n, n,
- y + offset, x + offset);
+ GLSLF("color[%d] += w * in%d[idx];\n", n, n);
} else {
- // invalid usage
- abort();
+ GLSLF("in0 = texture(tex, base + pt * vec2(%d.0, %d.0));\n", x, y);
+ GLSL(color += vec4(w) * in0;)
}
if (maybe_skippable)
@@ -150,7 +143,7 @@ static void polar_sample(struct gl_shader_cache *sc, struct scaler *scaler,
}
void pass_sample_polar(struct gl_shader_cache *sc, struct scaler *scaler,
- int components, int glsl_version)
+ int components, bool sup_gather)
{
GLSL(color = vec4(0.0);)
GLSLF("{\n");
@@ -158,7 +151,8 @@ void pass_sample_polar(struct gl_shader_cache *sc, struct scaler *scaler,
GLSL(vec2 base = pos - fcoord * pt;)
GLSLF("float w, d, wsum = 0.0;\n");
for (int n = 0; n < components; n++)
- GLSLF("vec4 c%d;\n", n);
+ GLSLF("vec4 in%d;\n", n);
+ GLSL(int idx;)
gl_sc_uniform_texture(sc, "lut", scaler->lut);
@@ -173,15 +167,14 @@ void pass_sample_polar(struct gl_shader_cache *sc, struct scaler *scaler,
// exactly when all four texels are within bounds
bool use_gather = sqrt(x*x + y*y) < scaler->kernel->radius_cutoff;
- // textureGather is only supported in GLSL 400+
- if (glsl_version < 400)
+ if (!sup_gather)
use_gather = false;
if (use_gather) {
// Gather the four surrounding texels simultaneously
for (int n = 0; n < components; n++) {
- GLSLF("c%d = textureGatherOffset(tex, base, ivec2(%d, %d), %d);\n",
- n, x, y, n);
+ GLSLF("in%d = textureGatherOffset(tex, base, "
+ "ivec2(%d, %d), %d);\n", n, x, y, n);
}
// Mix in all of the points with their weights
@@ -192,13 +185,14 @@ void pass_sample_polar(struct gl_shader_cache *sc, struct scaler *scaler,
static const int yo[4] = {1, 1, 0, 0};
if (x+xo[p] > bound || y+yo[p] > bound)
continue;
- polar_sample(sc, scaler, x+xo[p], y+yo[p], p, -1, components);
+ GLSLF("idx = %d;\n", p);
+ polar_sample(sc, scaler, x+xo[p], y+yo[p], components, true);
}
} else {
// switch to direct sampling instead, for efficiency/compatibility
for (int yy = y; yy <= bound && yy <= y+1; yy++) {
for (int xx = x; xx <= bound && xx <= x+1; xx++)
- polar_sample(sc, scaler, xx, yy, -1, -1, components);
+ polar_sample(sc, scaler, xx, yy, components, false);
}
}
}
@@ -223,20 +217,20 @@ void pass_compute_polar(struct gl_shader_cache *sc, struct scaler *scaler,
GLSL(vec2 fcoord = fract(pos * size - vec2(0.5));)
GLSL(vec2 base = pos - pt * fcoord;)
GLSL(ivec2 rel = ivec2(round((base - wbase) * size));)
+ GLSL(int idx;)
GLSLF("float w, d, wsum = 0.0;\n");
gl_sc_uniform_texture(sc, "lut", scaler->lut);
// Load all relevant texels into shmem
- gl_sc_enable_extension(sc, "GL_ARB_arrays_of_arrays");
for (int c = 0; c < components; c++)
- GLSLHF("shared float in%d[%d][%d];\n", c, ih, iw);
+ GLSLHF("shared float in%d[%d];\n", c, ih * iw);
GLSL(vec4 c;)
GLSLF("for (int y = int(gl_LocalInvocationID.y); y < %d; y += %d) {\n", ih, bh);
GLSLF("for (int x = int(gl_LocalInvocationID.x); x < %d; x += %d) {\n", iw, bw);
GLSLF("c = texture(tex, wbase + pt * vec2(x - %d, y - %d));\n", offset, offset);
for (int c = 0; c < components; c++)
- GLSLF("in%d[y][x] = c[%d];\n", c, c);
+ GLSLF("in%d[%d * y + x] = c[%d];\n", c, iw, c);
GLSLF("}}\n");
GLSL(groupMemoryBarrier();)
GLSL(barrier();)
@@ -244,8 +238,11 @@ void pass_compute_polar(struct gl_shader_cache *sc, struct scaler *scaler,
// Dispatch the actual samples
GLSLF("// scaler samples\n");
for (int y = 1-bound; y <= bound; y++) {
- for (int x = 1-bound; x <= bound; x++)
- polar_sample(sc, scaler, x, y, -1, offset, components);
+ for (int x = 1-bound; x <= bound; x++) {
+ GLSLF("idx = %d * rel.y + rel.x + %d;\n", iw,
+ iw * (y + offset) + x + offset);
+ polar_sample(sc, scaler, x, y, components, true);
+ }
}
GLSL(color = color / vec4(wsum);)
@@ -567,18 +564,19 @@ static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
{
GLSLF("// HDR tone mapping\n");
- // Desaturate the color using a coefficient dependent on the luminance
- GLSL(float luma = dot(dst_luma, color.rgb);)
- if (desat > 0) {
- GLSLF("float overbright = max(luma - %f, 1e-6) / max(luma, 1e-6);\n", desat);
- GLSL(color.rgb = mix(color.rgb, vec3(luma), overbright);)
- }
-
// To prevent discoloration due to out-of-bounds clipping, we need to make
// sure to reduce the value range as far as necessary to keep the entire
// signal in range, so tone map based on the brightest component.
GLSL(float sig = max(max(color.r, color.g), color.b);)
- GLSL(float sig_orig = sig;)
+
+ // Desaturate the color using a coefficient dependent on the signal
+ if (desat > 0) {
+ GLSL(float luma = dot(dst_luma, color.rgb);)
+ GLSL(float coeff = max(sig - 0.18, 1e-6) / max(sig, 1e-6););
+ GLSLF("coeff = pow(coeff, %f);\n", 10.0 / desat);
+ GLSL(color.rgb = mix(color.rgb, vec3(luma), coeff);)
+ GLSL(sig = mix(sig, luma, coeff);) // also make sure to update `sig`
+ }
if (!ref_peak) {
// For performance, we want to do as few atomic operations on global
@@ -614,6 +612,7 @@ static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
GLSLHF("const float sig_peak = %f;\n", ref_peak);
}
+ GLSL(float sig_orig = sig;)
switch (algo) {
case TONE_MAPPING_CLIP:
GLSLF("sig = %f * sig;\n", isnan(param) ? 1.0 : param);
@@ -627,7 +626,7 @@ static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
GLSLF("float b = (j*j - 2.0*j*sig_peak + sig_peak) / "
"max(1e-6, sig_peak - 1.0);\n");
GLSLF("float scale = (b*b + 2.0*b*j + j*j) / (b-a);\n");
- GLSL(sig = mix(sig, scale * (sig + a) / (sig + b), sig > j);)
+ GLSL(sig = sig > j ? scale * (sig + a) / (sig + b) : sig;)
break;
case TONE_MAPPING_REINHARD: {
@@ -770,6 +769,7 @@ static void prng_init(struct gl_shader_cache *sc, AVLFG *lfg)
// Initialize the PRNG by hashing the position + a random uniform
GLSL(vec3 _m = vec3(HOOKED_pos, random) + vec3(1.0);)
GLSL(float h = permute(permute(permute(_m.x)+_m.y)+_m.z);)
+ gl_sc_uniform_dynamic(sc);
gl_sc_uniform_f(sc, "random", (double)av_lfg_get(lfg) / UINT32_MAX);
}
diff --git a/video/out/opengl/video_shaders.h b/video/out/gpu/video_shaders.h
index 8345e4c..2ae2ac3 100644
--- a/video/out/opengl/video_shaders.h
+++ b/video/out/gpu/video_shaders.h
@@ -30,7 +30,7 @@ void sampler_prelude(struct gl_shader_cache *sc, int tex_num);
void pass_sample_separated_gen(struct gl_shader_cache *sc, struct scaler *scaler,
int d_x, int d_y);
void pass_sample_polar(struct gl_shader_cache *sc, struct scaler *scaler,
- int components, int glsl_version);
+ int components, bool sup_gather);
void pass_compute_polar(struct gl_shader_cache *sc, struct scaler *scaler,
int components, int bw, int bh, int iw, int ih);
void pass_sample_bicubic_fast(struct gl_shader_cache *sc);
diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c
index 3d03c47..fda40da 100644
--- a/video/out/opengl/common.c
+++ b/video/out/opengl/common.c
@@ -31,6 +31,7 @@
#include "common.h"
#include "common/common.h"
+#include "utils.h"
// This guesses if the current GL context is a suspected software renderer.
static bool is_software_gl(GL *gl)
@@ -49,14 +50,6 @@ static void GLAPIENTRY dummy_glBindFramebuffer(GLenum target, GLuint framebuffer
assert(framebuffer == 0);
}
-static bool check_ext(GL *gl, const char *name)
-{
- const char *exts = gl->extensions;
- char *s = strstr(exts, name);
- char *e = s ? s + strlen(name) : NULL;
- return s && (s == exts || s[-1] == ' ') && (e[0] == ' ' || !e[0]);
-}
-
#define FN_OFFS(name) offsetof(GL, name)
#define DEF_FN(name) {FN_OFFS(name), "gl" # name}
@@ -383,6 +376,15 @@ static const struct gl_functions gl_functions[] = {
{0},
},
},
+ // This one overrides GLX_SGI_swap_control on platforms using mesa. The
+ // only difference is that it supports glXSwapInterval(0).
+ {
+ .extension = "GLX_MESA_swap_control",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(SwapInterval, "glXSwapIntervalMESA"),
+ {0},
+ },
+ },
{
.extension = "WGL_EXT_swap_control",
.functions = (const struct gl_function[]) {
@@ -572,8 +574,8 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
if (ver_core)
must_exist = version >= ver_core;
- if (section->extension && check_ext(gl, section->extension))
- exists = true;
+ if (section->extension)
+ exists = gl_check_extension(gl->extensions, section->extension);
exists |= must_exist;
if (!exists)
@@ -623,7 +625,7 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
if (gl->es >= 300)
gl->glsl_version = 300;
} else {
- gl->glsl_version = 110;
+ gl->glsl_version = 120;
int glsl_major = 0, glsl_minor = 0;
if (shader && sscanf(shader, "%d.%d", &glsl_major, &glsl_minor) == 2)
gl->glsl_version = glsl_major * 100 + glsl_minor;
diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h
index 7b2e3ed..b9f582b 100644
--- a/video/out/opengl/common.h
+++ b/video/out/opengl/common.h
@@ -26,10 +26,10 @@
#include "common/msg.h"
#include "misc/bstr.h"
-#include "video/out/vo.h"
#include "video/csputils.h"
-
#include "video/mp_image.h"
+#include "video/out/vo.h"
+#include "video/out/gpu/ra.h"
#include "gl_headers.h"
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index fe454e9..cdaf632 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -1,10 +1,4 @@
/*
- * common OpenGL routines
- *
- * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de>
- * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c
- * gave me lots of good ideas.
- *
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
@@ -21,73 +15,10 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stddef.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdbool.h>
-#include <math.h>
-#include <assert.h>
-
+#include "options/m_config.h"
#include "context.h"
-#include "common/common.h"
-#include "options/options.h"
-#include "options/m_option.h"
-
-extern const struct mpgl_driver mpgl_driver_x11;
-extern const struct mpgl_driver mpgl_driver_x11egl;
-extern const struct mpgl_driver mpgl_driver_x11_probe;
-extern const struct mpgl_driver mpgl_driver_drm_egl;
-extern const struct mpgl_driver mpgl_driver_drm;
-extern const struct mpgl_driver mpgl_driver_cocoa;
-extern const struct mpgl_driver mpgl_driver_wayland;
-extern const struct mpgl_driver mpgl_driver_w32;
-extern const struct mpgl_driver mpgl_driver_angle;
-extern const struct mpgl_driver mpgl_driver_angle_es2;
-extern const struct mpgl_driver mpgl_driver_dxinterop;
-extern const struct mpgl_driver mpgl_driver_rpi;
-extern const struct mpgl_driver mpgl_driver_mali;
-extern const struct mpgl_driver mpgl_driver_vdpauglx;
-
-static const struct mpgl_driver *const backends[] = {
-#if HAVE_RPI
- &mpgl_driver_rpi,
-#endif
-#if HAVE_GL_COCOA
- &mpgl_driver_cocoa,
-#endif
-#if HAVE_EGL_ANGLE_WIN32
- &mpgl_driver_angle,
-#endif
-#if HAVE_GL_WIN32
- &mpgl_driver_w32,
-#endif
-#if HAVE_GL_DXINTEROP
- &mpgl_driver_dxinterop,
-#endif
-#if HAVE_GL_X11
- &mpgl_driver_x11_probe,
-#endif
-#if HAVE_EGL_X11
- &mpgl_driver_x11egl,
-#endif
-#if HAVE_GL_X11
- &mpgl_driver_x11,
-#endif
-#if HAVE_GL_WAYLAND
- &mpgl_driver_wayland,
-#endif
-#if HAVE_EGL_DRM
- &mpgl_driver_drm,
- &mpgl_driver_drm_egl,
-#endif
-#if HAVE_MALI_FBDEV
- &mpgl_driver_mali,
-#endif
-#if HAVE_VDPAU_GL_X11
- &mpgl_driver_vdpauglx,
-#endif
-};
+#include "ra_gl.h"
+#include "utils.h"
// 0-terminated list of desktop GL versions a backend should try to
// initialize. The first entry is the most preferred version.
@@ -103,140 +34,322 @@ const int mpgl_preferred_gl_versions[] = {
0
};
-int mpgl_find_backend(const char *name)
+enum {
+ FLUSH_NO = 0,
+ FLUSH_YES,
+ FLUSH_AUTO,
+};
+
+enum {
+ GLES_AUTO = 0,
+ GLES_YES,
+ GLES_NO,
+};
+
+struct opengl_opts {
+ int use_glfinish;
+ int waitvsync;
+ int vsync_pattern[2];
+ int swapinterval;
+ int early_flush;
+ int restrict_version;
+ int gles_mode;
+};
+
+#define OPT_BASE_STRUCT struct opengl_opts
+const struct m_sub_options opengl_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_FLAG("opengl-glfinish", use_glfinish, 0),
+ OPT_FLAG("opengl-waitvsync", waitvsync, 0),
+ OPT_INT("opengl-swapinterval", swapinterval, 0),
+ OPT_INTPAIR("opengl-check-pattern", vsync_pattern, 0),
+ OPT_INT("opengl-restrict", restrict_version, 0),
+ OPT_CHOICE("opengl-es", gles_mode, 0,
+ ({"auto", GLES_AUTO}, {"yes", GLES_YES}, {"no", GLES_NO})),
+ OPT_CHOICE("opengl-early-flush", early_flush, 0,
+ ({"no", FLUSH_NO}, {"yes", FLUSH_YES}, {"auto", FLUSH_AUTO})),
+
+ OPT_REPLACED("opengl-debug", "gpu-debug"),
+ OPT_REPLACED("opengl-sw", "gpu-sw"),
+ OPT_REPLACED("opengl-vsync-fences", "swapchain-depth"),
+ OPT_REPLACED("opengl-backend", "gpu-context"),
+ {0},
+ },
+ .defaults = &(const struct opengl_opts) {
+ .swapinterval = 1,
+ },
+ .size = sizeof(struct opengl_opts),
+};
+
+struct priv {
+ GL *gl;
+ struct mp_log *log;
+ struct ra_gl_ctx_params params;
+ struct opengl_opts *opts;
+ struct ra_swapchain_fns fns;
+ GLuint main_fb;
+ struct ra_tex *wrapped_fb; // corresponds to main_fb
+ // for debugging:
+ int frames_rendered;
+ unsigned int prev_sgi_sync_count;
+ // for gl_vsync_pattern
+ int last_pattern;
+ int matches, mismatches;
+ // for swapchain_depth simulation
+ GLsync *vsync_fences;
+ int num_vsync_fences;
+};
+
+bool ra_gl_ctx_test_version(struct ra_ctx *ctx, int version, bool es)
{
- if (name == NULL || strcmp(name, "auto") == 0)
- return -1;
- for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) {
- if (strcmp(backends[n]->name, name) == 0)
- return n;
+ bool ret;
+ struct opengl_opts *opts;
+ void *tmp = talloc_new(NULL);
+ opts = mp_get_config_group(tmp, ctx->global, &opengl_conf);
+
+ // Version too high
+ if (opts->restrict_version && version >= opts->restrict_version) {
+ ret = false;
+ goto done;
}
- return -2;
-}
-int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param)
-{
- if (bstr_equals0(param, "help")) {
- mp_info(log, "OpenGL windowing backends:\n");
- mp_info(log, " auto (autodetect)\n");
- for (int n = 0; n < MP_ARRAY_SIZE(backends); n++)
- mp_info(log, " %s\n", backends[n]->name);
- return M_OPT_EXIT;
+ switch (opts->gles_mode) {
+ case GLES_YES: ret = es; goto done;
+ case GLES_NO: ret = !es; goto done;
+ case GLES_AUTO: ret = true; goto done;
+ default: abort();
}
- char s[20];
- snprintf(s, sizeof(s), "%.*s", BSTR_P(param));
- return mpgl_find_backend(s) >= -1 ? 1 : M_OPT_INVALID;
+
+done:
+ talloc_free(tmp);
+ return ret;
}
-static void *get_native_display(void *pctx, const char *name)
+static void *get_native_display(void *priv, const char *name)
{
- MPGLContext *ctx = pctx;
- if (!ctx->native_display_type || !name)
+ struct priv *p = priv;
+ if (!p->params.native_display_type || !name)
+ return NULL;
+ if (strcmp(p->params.native_display_type, name) != 0)
return NULL;
- return strcmp(ctx->native_display_type, name) == 0 ? ctx->native_display : NULL;
+
+ return p->params.native_display;
}
-static MPGLContext *init_backend(struct vo *vo, const struct mpgl_driver *driver,
- bool probing, int vo_flags)
+void ra_gl_ctx_uninit(struct ra_ctx *ctx)
{
- MPGLContext *ctx = talloc_ptrtype(NULL, ctx);
- *ctx = (MPGLContext) {
- .gl = talloc_zero(ctx, GL),
- .vo = vo,
- .global = vo->global,
- .driver = driver,
- .log = vo->log,
+ if (ctx->swapchain) {
+ struct priv *p = ctx->swapchain->priv;
+ if (ctx->ra && p->wrapped_fb)
+ ra_tex_free(ctx->ra, &p->wrapped_fb);
+ talloc_free(ctx->swapchain);
+ ctx->swapchain = NULL;
+ }
+
+ ra_free(&ctx->ra);
+}
+
+static const struct ra_swapchain_fns ra_gl_swapchain_fns;
+
+bool ra_gl_ctx_init(struct ra_ctx *ctx, GL *gl, struct ra_gl_ctx_params params)
+{
+ struct ra_swapchain *sw = ctx->swapchain = talloc_ptrtype(NULL, sw);
+ *sw = (struct ra_swapchain) {
+ .ctx = ctx,
};
- if (probing)
- vo_flags |= VOFLAG_PROBING;
- bool old_probing = vo->probing;
- vo->probing = probing; // hack; kill it once backends are separate
- MP_VERBOSE(vo, "Initializing OpenGL backend '%s'\n", ctx->driver->name);
- ctx->priv = talloc_zero_size(ctx, ctx->driver->priv_size);
- if (ctx->driver->init(ctx, vo_flags) < 0) {
- vo->probing = old_probing;
- talloc_free(ctx);
- return NULL;
+
+ struct priv *p = sw->priv = talloc_ptrtype(sw, p);
+ *p = (struct priv) {
+ .gl = gl,
+ .log = ctx->log,
+ .params = params,
+ .opts = mp_get_config_group(p, ctx->global, &opengl_conf),
+ .fns = ra_gl_swapchain_fns,
+ };
+
+ sw->fns = &p->fns;
+
+ const struct ra_swapchain_fns *ext = p->params.external_swapchain;
+ if (ext) {
+ if (ext->color_depth)
+ p->fns.color_depth = ext->color_depth;
+ if (ext->screenshot)
+ p->fns.screenshot = ext->screenshot;
+ if (ext->start_frame)
+ p->fns.start_frame = ext->start_frame;
+ if (ext->submit_frame)
+ p->fns.submit_frame = ext->submit_frame;
+ if (ext->swap_buffers)
+ p->fns.swap_buffers = ext->swap_buffers;
}
- vo->probing = old_probing;
- if (!ctx->gl->version && !ctx->gl->es)
- goto cleanup;
+ if (!gl->version && !gl->es)
+ return false;
- if (probing && ctx->gl->es && (vo_flags & VOFLAG_NO_GLES)) {
- MP_VERBOSE(ctx->vo, "Skipping GLES backend.\n");
- goto cleanup;
+ if (gl->mpgl_caps & MPGL_CAP_SW) {
+ MP_WARN(p, "Suspected software renderer or indirect context.\n");
+ if (ctx->opts.probing && !ctx->opts.allow_sw)
+ return false;
}
- if (ctx->gl->mpgl_caps & MPGL_CAP_SW) {
- MP_WARN(ctx->vo, "Suspected software renderer or indirect context.\n");
- if (vo->probing && !(vo_flags & VOFLAG_SW))
- goto cleanup;
+ gl->debug_context = ctx->opts.debug;
+ gl->get_native_display_ctx = p;
+ gl->get_native_display = get_native_display;
+
+ if (gl->SwapInterval) {
+ gl->SwapInterval(p->opts->swapinterval);
+ } else {
+ MP_VERBOSE(p, "GL_*_swap_control extension missing.\n");
}
- ctx->gl->debug_context = !!(vo_flags & VOFLAG_GL_DEBUG);
+ ctx->ra = ra_create_gl(p->gl, ctx->log);
+ return !!ctx->ra;
+}
- ctx->gl->get_native_display_ctx = ctx;
- ctx->gl->get_native_display = get_native_display;
+void ra_gl_ctx_resize(struct ra_swapchain *sw, int w, int h, int fbo)
+{
+ struct priv *p = sw->priv;
+ if (p->main_fb == fbo && p->wrapped_fb && p->wrapped_fb->params.w == w
+ && p->wrapped_fb->params.h == h)
+ return;
- return ctx;
+ if (p->wrapped_fb)
+ ra_tex_free(sw->ctx->ra, &p->wrapped_fb);
-cleanup:
- mpgl_uninit(ctx);
- return NULL;
+ p->main_fb = fbo;
+ p->wrapped_fb = ra_create_wrapped_fb(sw->ctx->ra, fbo, w, h);
}
-// Create a VO window and create a GL context on it.
-// vo_flags: passed to the backend's create window function
-MPGLContext *mpgl_init(struct vo *vo, const char *backend_name, int vo_flags)
+int ra_gl_ctx_color_depth(struct ra_swapchain *sw)
{
- MPGLContext *ctx = NULL;
- int index = mpgl_find_backend(backend_name);
- if (index == -1) {
- for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) {
- ctx = init_backend(vo, backends[n], true, vo_flags);
- if (ctx)
- break;
- }
- // VO forced, but no backend is ok => force the first that works at all
- if (!ctx && !vo->probing) {
- for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) {
- ctx = init_backend(vo, backends[n], false, vo_flags);
- if (ctx)
- break;
- }
- }
- } else if (index >= 0) {
- ctx = init_backend(vo, backends[index], false, vo_flags);
- }
- return ctx;
+ struct priv *p = sw->priv;
+ GL *gl = p->gl;
+
+ if (!p->wrapped_fb)
+ return 0;
+
+ if ((gl->es < 300 && !gl->version) || !(gl->mpgl_caps & MPGL_CAP_FB))
+ return 0;
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, p->main_fb);
+
+ GLenum obj = gl->version ? GL_BACK_LEFT : GL_BACK;
+ if (p->main_fb)
+ obj = GL_COLOR_ATTACHMENT0;
+
+ GLint depth_g = 0;
+
+ gl->GetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, obj,
+ GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, &depth_g);
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ return depth_g;
}
-int mpgl_reconfig_window(struct MPGLContext *ctx)
+struct mp_image *ra_gl_ctx_screenshot(struct ra_swapchain *sw)
{
- return ctx->driver->reconfig(ctx);
+ struct priv *p = sw->priv;
+
+ assert(p->wrapped_fb);
+ struct mp_image *screen = gl_read_fbo_contents(p->gl, p->main_fb,
+ p->wrapped_fb->params.w,
+ p->wrapped_fb->params.h);
+
+ // OpenGL FB is also read in flipped order, so we need to flip when the
+ // rendering is *not* flipped, which in our case is whenever
+ // p->params.flipped is true. I hope that made sense
+ if (screen && p->params.flipped)
+ mp_image_vflip(screen);
+
+ return screen;
}
-int mpgl_control(struct MPGLContext *ctx, int *events, int request, void *arg)
+bool ra_gl_ctx_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
- return ctx->driver->control(ctx, events, request, arg);
+ struct priv *p = sw->priv;
+ out_fbo->tex = p->wrapped_fb;
+ out_fbo->flip = !p->params.flipped; // OpenGL FBs are normally flipped
+ return true;
}
-void mpgl_start_frame(struct MPGLContext *ctx)
+bool ra_gl_ctx_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
{
- if (ctx->driver->start_frame)
- ctx->driver->start_frame(ctx);
+ struct priv *p = sw->priv;
+ GL *gl = p->gl;
+
+ if (p->opts->use_glfinish)
+ gl->Finish();
+
+ if (gl->FenceSync && !p->params.external_swapchain) {
+ GLsync fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ if (fence)
+ MP_TARRAY_APPEND(p, p->vsync_fences, p->num_vsync_fences, fence);
+ }
+
+ switch (p->opts->early_flush) {
+ case FLUSH_AUTO:
+ if (frame->display_synced)
+ break;
+ // fall through
+ case FLUSH_YES:
+ gl->Flush();
+ }
+
+ return true;
}
-void mpgl_swap_buffers(struct MPGLContext *ctx)
+static void check_pattern(struct priv *p, int item)
{
- ctx->driver->swap_buffers(ctx);
+ int expected = p->opts->vsync_pattern[p->last_pattern];
+ if (item == expected) {
+ p->last_pattern++;
+ if (p->last_pattern >= 2)
+ p->last_pattern = 0;
+ p->matches++;
+ } else {
+ p->mismatches++;
+ MP_WARN(p, "wrong pattern, expected %d got %d (hit: %d, mis: %d)\n",
+ expected, item, p->matches, p->mismatches);
+ }
}
-void mpgl_uninit(MPGLContext *ctx)
+void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw)
{
- if (ctx)
- ctx->driver->uninit(ctx);
- talloc_free(ctx);
+ struct priv *p = sw->priv;
+ GL *gl = p->gl;
+
+ p->params.swap_buffers(sw->ctx);
+ p->frames_rendered++;
+
+ if (p->frames_rendered > 5 && !sw->ctx->opts.debug)
+ ra_gl_set_debug(sw->ctx->ra, false);
+
+ if ((p->opts->waitvsync || p->opts->vsync_pattern[0])
+ && gl->GetVideoSync)
+ {
+ unsigned int n1 = 0, n2 = 0;
+ gl->GetVideoSync(&n1);
+ if (p->opts->waitvsync)
+ gl->WaitVideoSync(2, (n1 + 1) % 2, &n2);
+ int step = n1 - p->prev_sgi_sync_count;
+ p->prev_sgi_sync_count = n1;
+ MP_DBG(p, "Flip counts: %u->%u, step=%d\n", n1, n2, step);
+ if (p->opts->vsync_pattern[0])
+ check_pattern(p, step);
+ }
+
+ while (p->num_vsync_fences >= sw->ctx->opts.swapchain_depth) {
+ gl->ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9);
+ gl->DeleteSync(p->vsync_fences[0]);
+ MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0);
+ }
}
+
+static const struct ra_swapchain_fns ra_gl_swapchain_fns = {
+ .color_depth = ra_gl_ctx_color_depth,
+ .screenshot = ra_gl_ctx_screenshot,
+ .start_frame = ra_gl_ctx_start_frame,
+ .submit_frame = ra_gl_ctx_submit_frame,
+ .swap_buffers = ra_gl_ctx_swap_buffers,
+};
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index 229c5ef..95ed374 100644
--- a/video/out/opengl/context.h
+++ b/video/out/opengl/context.h
@@ -1,116 +1,56 @@
-/*
- * common OpenGL routines
- *
- * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de>
- * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c
- * gave me lots of good ideas.
- *
- * This file is part of mpv.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MP_GL_CONTEXT_H_
-#define MP_GL_CONTEXT_H_
+#pragma once
+#include "common/global.h"
+#include "video/out/gpu/context.h"
#include "common.h"
-enum {
- VOFLAG_GLES = 1 << 0, // Hint to create a GLES context
- VOFLAG_NO_GLES = 1 << 1, // Hint to create a desktop GL context
- VOFLAG_GL_DEBUG = 1 << 2, // Hint to request debug OpenGL context
- VOFLAG_ALPHA = 1 << 3, // Hint to request alpha framebuffer
- VOFLAG_SW = 1 << 4, // Hint to accept a software GL renderer
- VOFLAG_PROBING = 1 << 6, // The backend is being auto-probed.
- VOFLAG_GLES2 = 1 << 7, // Hint for GLESv2 (needs VOFLAG_GLES)
-};
-
extern const int mpgl_preferred_gl_versions[];
-struct MPGLContext;
-
-// A windowing backend (like X11, win32, ...), which provides OpenGL rendering.
-struct mpgl_driver {
- const char *name;
-
- // Size of the struct allocated for MPGLContext.priv
- int priv_size;
-
- // Init the GL context and possibly the underlying VO backend.
- // The created context should be compatible to GL 3.2 core profile, but
- // some other GL versions are supported as well (e.g. GL 2.1 or GLES 2).
- // Return 0 on success, negative value (-1) on error.
- int (*init)(struct MPGLContext *ctx, int vo_flags);
-
- // Resize the window, or create a new window if there isn't one yet.
- // Currently, there is an unfortunate interaction with ctx->vo, and
- // display size etc. are determined by it.
- // Return 0 on success, negative value (-1) on error.
- int (*reconfig)(struct MPGLContext *ctx);
-
- // Called when rendering starts. The backend can map or resize the
- // framebuffer, or update GL.main_fb. swap_buffers() ends the frame.
- // Optional.
- void (*start_frame)(struct MPGLContext *ctx);
-
- // Present the frame.
- void (*swap_buffers)(struct MPGLContext *ctx);
-
- // This behaves exactly like vo_driver.control().
- int (*control)(struct MPGLContext *ctx, int *events, int request, void *arg);
-
- // These behave exactly like vo_driver.wakeup/wait_events. They are
- // optional.
- void (*wakeup)(struct MPGLContext *ctx);
- void (*wait_events)(struct MPGLContext *ctx, int64_t until_time_us);
-
- // Destroy the GL context and possibly the underlying VO backend.
- void (*uninit)(struct MPGLContext *ctx);
-};
-
-typedef struct MPGLContext {
- GL *gl;
- struct vo *vo;
- const struct mpgl_driver *driver;
- struct mpv_global *global;
- struct mp_log *log;
-
- // For hwdec_vaegl.c.
+// Returns whether or not a candidate GL version should be accepted or not
+// (based on the --opengl opts). Implementations may call this before
+// ra_gl_ctx_init if they wish to probe for multiple possible GL versions.
+bool ra_gl_ctx_test_version(struct ra_ctx *ctx, int version, bool es);
+
+// These are a set of helpers for ra_ctx providers based on ra_gl.
+// The init function also initializes ctx->ra and ctx->swapchain, so the user
+// doesn't have to do this manually. (Similarly, the uninit function will
+// clean them up)
+
+struct ra_gl_ctx_params {
+ // Set to the platform-specific function to swap buffers, like
+ // glXSwapBuffers, eglSwapBuffers etc. This will be called by
+ // ra_gl_ctx_swap_buffers. Required unless you either never call that
+ // function or if you override it yourself.
+ void (*swap_buffers)(struct ra_ctx *ctx);
+
+ // Set to false if the implementation follows normal GL semantics, which is
+ // upside down. Set to true if it does *not*, i.e. if rendering is right
+ // side up
+ bool flipped;
+
+ // If this is set to non-NULL, then the ra_gl_ctx will consider the GL
+ // implementation to be using an external swapchain, which disables the
+ // software simulation of --swapchain-depth. Any functions defined by this
+ // ra_swapchain_fns structs will entirely replace the equivalent ra_gl_ctx
+ // functions in the resulting ra_swapchain.
+ const struct ra_swapchain_fns *external_swapchain;
+
+ // For hwdec_vaegl.c:
const char *native_display_type;
void *native_display;
+};
- // Flip the rendered image vertically. This is useful for dxinterop.
- bool flip_v;
-
- // framebuffer to render to (normally 0)
- GLuint main_fb;
-
- // For free use by the mpgl_driver.
- void *priv;
-} MPGLContext;
-
-MPGLContext *mpgl_init(struct vo *vo, const char *backend_name, int vo_flags);
-void mpgl_uninit(MPGLContext *ctx);
-int mpgl_reconfig_window(struct MPGLContext *ctx);
-int mpgl_control(struct MPGLContext *ctx, int *events, int request, void *arg);
-void mpgl_start_frame(struct MPGLContext *ctx);
-void mpgl_swap_buffers(struct MPGLContext *ctx);
-
-int mpgl_find_backend(const char *name);
+void ra_gl_ctx_uninit(struct ra_ctx *ctx);
+bool ra_gl_ctx_init(struct ra_ctx *ctx, GL *gl, struct ra_gl_ctx_params params);
-struct m_option;
-int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param);
+// Call this any time the window size or main framebuffer changes
+void ra_gl_ctx_resize(struct ra_swapchain *sw, int w, int h, int fbo);
-#endif
+// These functions are normally set in the ra_swapchain->fns, but if an
+// implementation has a need to override this fns struct with custom functions
+// for whatever reason, these can be used to inherit the original behavior.
+int ra_gl_ctx_color_depth(struct ra_swapchain *sw);
+struct mp_image *ra_gl_ctx_screenshot(struct ra_swapchain *sw);
+bool ra_gl_ctx_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo);
+bool ra_gl_ctx_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame);
+void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw);
diff --git a/video/out/opengl/context_android.c b/video/out/opengl/context_android.c
new file mode 100644
index 0000000..a2acce2
--- /dev/null
+++ b/video/out/opengl/context_android.c
@@ -0,0 +1,152 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <libavcodec/jni.h>
+#include <android/native_window_jni.h>
+
+#include "egl_helpers.h"
+
+#include "common/common.h"
+#include "options/m_config.h"
+#include "context.h"
+
+struct priv {
+ struct GL gl;
+ EGLDisplay egl_display;
+ EGLConfig egl_config;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+ ANativeWindow *native_window;
+};
+
+static void android_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+}
+
+static void android_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
+ if (p->egl_surface) {
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ }
+ if (p->egl_context)
+ eglDestroyContext(p->egl_display, p->egl_context);
+
+ if (p->native_window) {
+ ANativeWindow_release(p->native_window);
+ p->native_window = NULL;
+ }
+}
+
+static bool android_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+
+ jobject surface = (jobject)(intptr_t)ctx->vo->opts->WinID;
+ JavaVM *vm = (JavaVM *)av_jni_get_java_vm(NULL);
+ JNIEnv *env;
+ int ret = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6);
+ if (ret == JNI_EDETACHED) {
+ if ((*vm)->AttachCurrentThread(vm, &env, NULL) != 0) {
+ MP_FATAL(ctx, "Could not attach java VM.\n");
+ goto fail;
+ }
+ }
+ p->native_window = ANativeWindow_fromSurface(env, surface);
+ (*vm)->DetachCurrentThread(vm);
+
+ p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_FATAL(ctx, "EGL failed to initialize.\n");
+ goto fail;
+ }
+
+ EGLConfig config;
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &config))
+ goto fail;
+
+ EGLint format;
+ eglGetConfigAttrib(p->egl_display, config, EGL_NATIVE_VISUAL_ID, &format);
+ ANativeWindow_setBuffersGeometry(p->native_window, 0, 0, format);
+
+ p->egl_surface = eglCreateWindowSurface(p->egl_display, config,
+ (EGLNativeWindowType)p->native_window, NULL);
+
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
+ goto fail;
+ }
+
+ if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
+ p->egl_context)) {
+ MP_FATAL(ctx, "Failed to set context!\n");
+ goto fail;
+ }
+
+ mpegl_load_functions(&p->gl, ctx->log);
+
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = android_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto fail;
+
+ return true;
+fail:
+ android_uninit(ctx);
+ return false;
+}
+
+static bool android_reconfig(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ int w, h;
+
+ if (!eglQuerySurface(p->egl_display, p->egl_surface, EGL_WIDTH, &w) ||
+ !eglQuerySurface(p->egl_display, p->egl_surface, EGL_HEIGHT, &h)) {
+ MP_FATAL(ctx, "Failed to get height and width!\n");
+ return false;
+ }
+
+ ctx->vo->dwidth = w;
+ ctx->vo->dheight = h;
+ ra_gl_ctx_resize(ctx->swapchain, w, h, 0);
+ return true;
+}
+
+static int android_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ return VO_NOTIMPL;
+}
+
+const struct ra_ctx_fns ra_ctx_android = {
+ .type = "opengl",
+ .name = "android",
+ .reconfig = android_reconfig,
+ .control = android_control,
+ .init = android_init,
+ .uninit = android_uninit,
+};
diff --git a/video/out/opengl/context_angle.c b/video/out/opengl/context_angle.c
index f249b74..986a503 100644
--- a/video/out/opengl/context_angle.c
+++ b/video/out/opengl/context_angle.c
@@ -24,13 +24,14 @@
#include "angle_dynamic.h"
#include "egl_helpers.h"
-#include "d3d11_helpers.h"
+#include "video/out/gpu/d3d11_helpers.h"
#include "common/common.h"
#include "options/m_config.h"
#include "video/out/w32_common.h"
#include "osdep/windows_utils.h"
#include "context.h"
+#include "utils.h"
#ifndef EGL_D3D_TEXTURE_ANGLE
#define EGL_D3D_TEXTURE_ANGLE 0x33A3
@@ -52,8 +53,6 @@ struct angle_opts {
int d3d11_warp;
int d3d11_feature_level;
int egl_windowing;
- int swapchain_length; // Currently only works with DXGI 1.2+
- int max_frame_latency;
int flip;
};
@@ -77,9 +76,9 @@ const struct m_sub_options angle_conf = {
({"auto", -1},
{"no", 0},
{"yes", 1})),
- OPT_INTRANGE("angle-swapchain-length", swapchain_length, 0, 2, 16),
- OPT_INTRANGE("angle-max-frame-latency", max_frame_latency, 0, 1, 16),
OPT_FLAG("angle-flip", flip, 0),
+ OPT_REPLACED("angle-max-frame-latency", "swapchain-depth"),
+ OPT_REMOVED("angle-swapchain-length", "controlled by --swapchain-depth"),
{0}
},
.defaults = &(const struct angle_opts) {
@@ -87,14 +86,14 @@ const struct m_sub_options angle_conf = {
.d3d11_warp = -1,
.d3d11_feature_level = D3D_FEATURE_LEVEL_11_0,
.egl_windowing = -1,
- .swapchain_length = 6,
- .max_frame_latency = 3,
.flip = 1,
},
.size = sizeof(struct angle_opts),
};
struct priv {
+ GL gl;
+
IDXGISwapChain *dxgi_swapchain;
ID3D11Device *d3d11_device;
@@ -110,20 +109,21 @@ struct priv {
int sc_width, sc_height; // Swap chain width and height
int swapinterval;
+ bool flipped;
struct angle_opts *opts;
};
-static __thread struct MPGLContext *current_ctx;
+static __thread struct ra_ctx *current_ctx;
-static void update_sizes(MPGLContext *ctx)
+static void update_sizes(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
p->sc_width = ctx->vo->dwidth ? ctx->vo->dwidth : 1;
p->sc_height = ctx->vo->dheight ? ctx->vo->dheight : 1;
}
-static void d3d11_backbuffer_release(MPGLContext *ctx)
+static void d3d11_backbuffer_release(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -137,7 +137,7 @@ static void d3d11_backbuffer_release(MPGLContext *ctx)
SAFE_RELEASE(p->d3d11_backbuffer);
}
-static bool d3d11_backbuffer_get(MPGLContext *ctx)
+static bool d3d11_backbuffer_get(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -168,7 +168,7 @@ static bool d3d11_backbuffer_get(MPGLContext *ctx)
return true;
}
-static void d3d11_backbuffer_resize(MPGLContext *ctx)
+static void d3d11_backbuffer_resize(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -197,7 +197,7 @@ static void d3d11_backbuffer_resize(MPGLContext *ctx)
MP_FATAL(vo, "Couldn't get back buffer after resize\n");
}
-static void d3d11_device_destroy(MPGLContext *ctx)
+static void d3d11_device_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -215,7 +215,7 @@ static void d3d11_device_destroy(MPGLContext *ctx)
SAFE_RELEASE(p->d3d11_device);
}
-static bool d3d11_device_create(MPGLContext *ctx, int flags)
+static bool d3d11_device_create(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -226,7 +226,7 @@ static bool d3d11_device_create(MPGLContext *ctx, int flags)
.force_warp = o->d3d11_warp == 1,
.max_feature_level = o->d3d11_feature_level,
.min_feature_level = D3D_FEATURE_LEVEL_9_3,
- .max_frame_latency = o->max_frame_latency,
+ .max_frame_latency = ctx->opts.swapchain_depth,
};
if (!mp_d3d11_create_present_device(vo->log, &device_opts, &p->d3d11_device))
return false;
@@ -262,7 +262,7 @@ static bool d3d11_device_create(MPGLContext *ctx, int flags)
return true;
}
-static void d3d11_swapchain_surface_destroy(MPGLContext *ctx)
+static void d3d11_swapchain_surface_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -277,7 +277,7 @@ static void d3d11_swapchain_surface_destroy(MPGLContext *ctx)
ID3D11DeviceContext_Flush(p->d3d11_context);
}
-static bool d3d11_swapchain_surface_create(MPGLContext *ctx, int flags)
+static bool d3d11_swapchain_surface_create(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -292,7 +292,9 @@ static bool d3d11_swapchain_surface_create(MPGLContext *ctx, int flags)
.width = p->sc_width,
.height = p->sc_height,
.flip = o->flip,
- .length = o->swapchain_length,
+ // Add one frame for the backbuffer and one frame of "slack" to reduce
+ // contention with the window manager when acquiring the backbuffer
+ .length = ctx->opts.swapchain_depth + 2,
.usage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT,
};
if (!mp_d3d11_create_swapchain(p->d3d11_device, vo->log, &swapchain_opts,
@@ -301,8 +303,7 @@ static bool d3d11_swapchain_surface_create(MPGLContext *ctx, int flags)
if (!d3d11_backbuffer_get(ctx))
goto fail;
- // EGL_D3D_TEXTURE_ANGLE pbuffers are always flipped vertically
- ctx->flip_v = true;
+ p->flipped = true;
return true;
fail:
@@ -310,7 +311,7 @@ fail:
return false;
}
-static void d3d9_device_destroy(MPGLContext *ctx)
+static void d3d9_device_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -319,7 +320,7 @@ static void d3d9_device_destroy(MPGLContext *ctx)
p->egl_display = EGL_NO_DISPLAY;
}
-static bool d3d9_device_create(MPGLContext *ctx, int flags)
+static bool d3d9_device_create(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -348,7 +349,7 @@ static bool d3d9_device_create(MPGLContext *ctx, int flags)
return true;
}
-static void egl_window_surface_destroy(MPGLContext *ctx)
+static void egl_window_surface_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->egl_window) {
@@ -357,7 +358,7 @@ static void egl_window_surface_destroy(MPGLContext *ctx)
}
}
-static bool egl_window_surface_create(MPGLContext *ctx, int flags)
+static bool egl_window_surface_create(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -374,7 +375,7 @@ static bool egl_window_surface_create(MPGLContext *ctx, int flags)
EGL_SURFACE_ORIENTATION_ANGLE);
MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len,
EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE);
- ctx->flip_v = true;
+ p->flipped = true;
MP_VERBOSE(vo, "Rendering flipped.\n");
}
}
@@ -396,7 +397,7 @@ fail:
return false;
}
-static void context_destroy(struct MPGLContext *ctx)
+static void context_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->egl_context) {
@@ -407,7 +408,7 @@ static void context_destroy(struct MPGLContext *ctx)
p->egl_context = EGL_NO_CONTEXT;
}
-static bool context_init(struct MPGLContext *ctx, int flags)
+static bool context_init(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -421,8 +422,8 @@ static bool context_init(struct MPGLContext *ctx, int flags)
if (exts)
MP_DBG(vo, "EGL extensions: %s\n", exts);
- if (!mpegl_create_context(p->egl_display, vo->log, flags | VOFLAG_GLES,
- &p->egl_context, &p->egl_config))
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context,
+ &p->egl_config))
{
MP_FATAL(vo, "Could not create EGL context!\n");
goto fail;
@@ -434,10 +435,12 @@ fail:
return false;
}
-static void angle_uninit(struct MPGLContext *ctx)
+static void angle_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
DwmEnableMMCSS(FALSE);
// Uninit the EGL surface implementation that is being used. Note: This may
@@ -474,17 +477,88 @@ static int GLAPIENTRY angle_swap_interval(int interval)
}
}
-static int angle_init(struct MPGLContext *ctx, int flags)
+static void d3d11_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ // Calling Present() on a flip-sequential swap chain will silently change
+ // the underlying storage of the back buffer to point to the next buffer in
+ // the chain. This results in the RTVs for the back buffer becoming
+ // unbound. Since ANGLE doesn't know we called Present(), it will continue
+ // using the unbound RTVs, so we must save and restore them ourselves.
+ ID3D11RenderTargetView *rtvs[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {0};
+ ID3D11DepthStencilView *dsv = NULL;
+ ID3D11DeviceContext_OMGetRenderTargets(p->d3d11_context,
+ MP_ARRAY_SIZE(rtvs), rtvs, &dsv);
+
+ HRESULT hr = IDXGISwapChain_Present(p->dxgi_swapchain, p->swapinterval, 0);
+ if (FAILED(hr))
+ MP_FATAL(ctx->vo, "Couldn't present: %s\n", mp_HRESULT_to_str(hr));
+
+ // Restore the RTVs and release the objects
+ ID3D11DeviceContext_OMSetRenderTargets(p->d3d11_context,
+ MP_ARRAY_SIZE(rtvs), rtvs, dsv);
+ for (int i = 0; i < MP_ARRAY_SIZE(rtvs); i++)
+ SAFE_RELEASE(rtvs[i]);
+ SAFE_RELEASE(dsv);
+}
+
+static void egl_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_window);
+}
+
+static void angle_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ if (p->dxgi_swapchain)
+ d3d11_swap_buffers(ctx);
+ else
+ egl_swap_buffers(ctx);
+}
+
+
+static int angle_color_depth(struct ra_swapchain *sw)
+{
+ // Only 8-bit output is supported at the moment
+ return 8;
+}
+
+static struct mp_image *angle_screenshot(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->ctx->priv;
+ if (p->dxgi_swapchain) {
+ struct mp_image *img = mp_d3d11_screenshot(p->dxgi_swapchain);
+ if (img)
+ return img;
+ }
+ return ra_gl_ctx_screenshot(sw);
+}
+
+static bool angle_submit_frame(struct ra_swapchain *sw,
+ const struct vo_frame *frame)
+{
+ struct priv *p = sw->ctx->priv;
+ bool ret = ra_gl_ctx_submit_frame(sw, frame);
+ if (p->d3d11_context) {
+ // DXGI Present doesn't flush the immediate context, which can make
+ // timers inaccurate, since the end queries might not be sent until the
+ // next frame. Fix this by flushing the context now.
+ ID3D11DeviceContext_Flush(p->d3d11_context);
+ }
+ return ret;
+}
+
+static bool angle_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
struct vo *vo = ctx->vo;
+ GL *gl = &p->gl;
p->opts = mp_get_config_group(ctx, ctx->global, &angle_conf);
struct angle_opts *o = p->opts;
- // DWM MMCSS cargo-cult. The dxinterop backend also does this.
- DwmEnableMMCSS(TRUE);
-
if (!angle_load()) {
MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n");
goto fail;
@@ -493,19 +567,19 @@ static int angle_init(struct MPGLContext *ctx, int flags)
// Create the underlying EGL device implementation
bool context_ok = false;
if ((!context_ok && !o->renderer) || o->renderer == RENDERER_D3D11) {
- context_ok = d3d11_device_create(ctx, flags);
+ context_ok = d3d11_device_create(ctx);
if (context_ok) {
- context_ok = context_init(ctx, flags);
+ context_ok = context_init(ctx);
if (!context_ok)
d3d11_device_destroy(ctx);
}
}
if ((!context_ok && !o->renderer) || o->renderer == RENDERER_D3D9) {
- context_ok = d3d9_device_create(ctx, flags);
+ context_ok = d3d9_device_create(ctx);
if (context_ok) {
MP_VERBOSE(vo, "Using Direct3D 9\n");
- context_ok = context_init(ctx, flags);
+ context_ok = context_init(ctx);
if (!context_ok)
d3d9_device_destroy(ctx);
}
@@ -519,181 +593,74 @@ static int angle_init(struct MPGLContext *ctx, int flags)
// Create the underlying EGL surface implementation
bool surface_ok = false;
if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 0) {
- surface_ok = d3d11_swapchain_surface_create(ctx, flags);
+ surface_ok = d3d11_swapchain_surface_create(ctx);
}
if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 1) {
- surface_ok = egl_window_surface_create(ctx, flags);
+ surface_ok = egl_window_surface_create(ctx);
if (surface_ok)
MP_VERBOSE(vo, "Using EGL windowing\n");
}
if (!surface_ok)
goto fail;
- mpegl_load_functions(ctx->gl, vo->log);
+ mpegl_load_functions(gl, vo->log);
current_ctx = ctx;
- ctx->gl->SwapInterval = angle_swap_interval;
-
- return 0;
-fail:
- angle_uninit(ctx);
- return -1;
-}
-
-static int angle_reconfig(struct MPGLContext *ctx)
-{
- vo_w32_config(ctx->vo);
- return 0;
-}
+ gl->SwapInterval = angle_swap_interval;
-static struct mp_image *d3d11_screenshot(MPGLContext *ctx)
-{
- struct priv *p = ctx->priv;
- ID3D11Texture2D *frontbuffer = NULL;
- ID3D11Texture2D *staging = NULL;
- struct mp_image *img = NULL;
- HRESULT hr;
-
- if (!p->dxgi_swapchain)
- goto done;
-
- // Validate the swap chain. This screenshot method will only work on DXGI
- // 1.2+ flip/sequential swap chains. It's probably not possible at all with
- // discard swap chains, since by definition, the backbuffer contents is
- // discarded on Present().
- DXGI_SWAP_CHAIN_DESC scd;
- hr = IDXGISwapChain_GetDesc(p->dxgi_swapchain, &scd);
- if (FAILED(hr))
- goto done;
- if (scd.SwapEffect != DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL)
- goto done;
-
- // Get the last buffer that was presented with Present(). This should be
- // the n-1th buffer for a swap chain of length n.
- hr = IDXGISwapChain_GetBuffer(p->dxgi_swapchain, scd.BufferCount - 1,
- &IID_ID3D11Texture2D, (void**)&frontbuffer);
- if (FAILED(hr))
- goto done;
-
- D3D11_TEXTURE2D_DESC td;
- ID3D11Texture2D_GetDesc(frontbuffer, &td);
- if (td.SampleDesc.Count > 1)
- goto done;
-
- // Validate the backbuffer format and convert to an mpv IMGFMT
- enum mp_imgfmt fmt;
- switch (td.Format) {
- case DXGI_FORMAT_B8G8R8A8_UNORM: fmt = IMGFMT_BGR0; break;
- case DXGI_FORMAT_R8G8B8A8_UNORM: fmt = IMGFMT_RGB0; break;
- default:
- goto done;
- }
-
- // Create a staging texture based on the frontbuffer with CPU access
- td.BindFlags = 0;
- td.MiscFlags = 0;
- td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
- td.Usage = D3D11_USAGE_STAGING;
- hr = ID3D11Device_CreateTexture2D(p->d3d11_device, &td, 0, &staging);
- if (FAILED(hr))
- goto done;
-
- ID3D11DeviceContext_CopyResource(p->d3d11_context,
- (ID3D11Resource*)staging, (ID3D11Resource*)frontbuffer);
-
- // Attempt to map the staging texture to CPU-accessible memory
- D3D11_MAPPED_SUBRESOURCE lock;
- hr = ID3D11DeviceContext_Map(p->d3d11_context, (ID3D11Resource*)staging,
- 0, D3D11_MAP_READ, 0, &lock);
- if (FAILED(hr))
- goto done;
-
- img = mp_image_alloc(fmt, td.Width, td.Height);
- if (!img)
- return NULL;
- for (int i = 0; i < td.Height; i++) {
- memcpy(img->planes[0] + img->stride[0] * i,
- (char*)lock.pData + lock.RowPitch * i, td.Width * 4);
- }
-
- ID3D11DeviceContext_Unmap(p->d3d11_context, (ID3D11Resource*)staging, 0);
-
-done:
- SAFE_RELEASE(frontbuffer);
- SAFE_RELEASE(staging);
- return img;
-}
+ // Custom swapchain impl for the D3D11 swapchain-based surface
+ static const struct ra_swapchain_fns dxgi_swapchain_fns = {
+ .color_depth = angle_color_depth,
+ .screenshot = angle_screenshot,
+ .submit_frame = angle_submit_frame,
+ };
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = angle_swap_buffers,
+ .flipped = p->flipped,
+ .external_swapchain = p->dxgi_swapchain ? &dxgi_swapchain_fns : NULL,
+ };
-static int angle_control(MPGLContext *ctx, int *events, int request, void *arg)
-{
- struct priv *p = ctx->priv;
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto fail;
- // Try a D3D11-specific method of taking a window screenshot
- if (request == VOCTRL_SCREENSHOT_WIN) {
- struct mp_image *img = d3d11_screenshot(ctx);
- if (img) {
- *(struct mp_image **)arg = img;
- return true;
- }
- }
+ DwmEnableMMCSS(TRUE); // DWM MMCSS cargo-cult. The dxgl backend also does this.
- int r = vo_w32_control(ctx->vo, events, request, arg);
- if (*events & VO_EVENT_RESIZE) {
- if (p->dxgi_swapchain)
- d3d11_backbuffer_resize(ctx);
- else
- eglWaitClient(); // Should get ANGLE to resize its swapchain
- }
- return r;
+ return true;
+fail:
+ angle_uninit(ctx);
+ return false;
}
-static void d3d11_swap_buffers(MPGLContext *ctx)
+static void resize(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
-
- // Calling Present() on a flip-sequential swap chain will silently change
- // the underlying storage of the back buffer to point to the next buffer in
- // the chain. This results in the RTVs for the back buffer becoming
- // unbound. Since ANGLE doesn't know we called Present(), it will continue
- // using the unbound RTVs, so we must save and restore them ourselves.
- ID3D11RenderTargetView *rtvs[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {0};
- ID3D11DepthStencilView *dsv = NULL;
- ID3D11DeviceContext_OMGetRenderTargets(p->d3d11_context,
- MP_ARRAY_SIZE(rtvs), rtvs, &dsv);
-
- HRESULT hr = IDXGISwapChain_Present(p->dxgi_swapchain, p->swapinterval, 0);
- if (FAILED(hr))
- MP_FATAL(ctx->vo, "Couldn't present: %s\n", mp_HRESULT_to_str(hr));
-
- // Restore the RTVs and release the objects
- ID3D11DeviceContext_OMSetRenderTargets(p->d3d11_context,
- MP_ARRAY_SIZE(rtvs), rtvs, dsv);
- for (int i = 0; i < MP_ARRAY_SIZE(rtvs); i++)
- SAFE_RELEASE(rtvs[i]);
- SAFE_RELEASE(dsv);
+ if (p->dxgi_swapchain)
+ d3d11_backbuffer_resize(ctx);
+ else
+ eglWaitClient(); // Should get ANGLE to resize its swapchain
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
}
-static void egl_swap_buffers(MPGLContext *ctx)
+static bool angle_reconfig(struct ra_ctx *ctx)
{
- struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl_display, p->egl_window);
+ vo_w32_config(ctx->vo);
+ resize(ctx);
+ return true;
}
-static void angle_swap_buffers(MPGLContext *ctx)
+static int angle_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
- struct priv *p = ctx->priv;
- if (p->dxgi_swapchain)
- d3d11_swap_buffers(ctx);
- else
- egl_swap_buffers(ctx);
+ int ret = vo_w32_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
}
-const struct mpgl_driver mpgl_driver_angle = {
+const struct ra_ctx_fns ra_ctx_angle = {
+ .type = "opengl",
.name = "angle",
- .priv_size = sizeof(struct priv),
.init = angle_init,
.reconfig = angle_reconfig,
- .swap_buffers = angle_swap_buffers,
.control = angle_control,
.uninit = angle_uninit,
};
diff --git a/video/out/opengl/context_cocoa.c b/video/out/opengl/context_cocoa.c
index 1d9a10c..2256d31 100644
--- a/video/out/opengl/context_cocoa.c
+++ b/video/out/opengl/context_cocoa.c
@@ -36,6 +36,7 @@ const struct m_sub_options cocoa_conf = {
};
struct priv {
+ GL gl;
CGLPixelFormatObj pix;
CGLContextObj ctx;
@@ -62,7 +63,7 @@ static void *cocoa_glgetaddr(const char *s)
return ret;
}
-static CGLError test_gl_version(struct MPGLContext *ctx, CGLOpenGLProfile ver)
+static CGLError test_gl_version(struct ra_ctx *ctx, CGLOpenGLProfile ver)
{
struct priv *p = ctx->priv;
@@ -107,9 +108,10 @@ error_out:
return err;
}
-static bool create_gl_context(struct MPGLContext *ctx, int vo_flags)
+static bool create_gl_context(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ GL *gl = &p->gl;
CGLError err;
CGLOpenGLProfile gl_versions[] = {
@@ -132,60 +134,83 @@ static bool create_gl_context(struct MPGLContext *ctx, int vo_flags)
vo_cocoa_set_opengl_ctx(ctx->vo, p->ctx);
CGLSetCurrentContext(p->ctx);
- if (vo_flags & VOFLAG_ALPHA)
+ if (ctx->opts.want_alpha)
CGLSetParameter(p->ctx, kCGLCPSurfaceOpacity, &(GLint){0});
- mpgl_load_functions(ctx->gl, (void *)cocoa_glgetaddr, NULL, ctx->vo->log);
+ mpgl_load_functions(gl, (void *)cocoa_glgetaddr, NULL, ctx->vo->log);
+ gl->SwapInterval = set_swap_interval;
CGLReleasePixelFormat(p->pix);
return true;
}
-static void cocoa_uninit(MPGLContext *ctx)
+static void cocoa_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
CGLReleaseContext(p->ctx);
vo_cocoa_uninit(ctx->vo);
}
-static int cocoa_init(MPGLContext *ctx, int vo_flags)
+static void cocoa_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ GL *gl = &p->gl;
+ vo_cocoa_swap_buffers(ctx->vo);
+ gl->Flush();
+}
+
+static bool cocoa_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ GL *gl = &p->gl;
p->opts = mp_get_config_group(ctx, ctx->global, &cocoa_conf);
vo_cocoa_init(ctx->vo);
- if (!create_gl_context(ctx, vo_flags))
- return -1;
+ if (!create_gl_context(ctx))
+ goto fail;
+
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = cocoa_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto fail;
+
+ return true;
- ctx->gl->SwapInterval = set_swap_interval;
- return 0;
+fail:
+ cocoa_uninit(ctx);
+ return false;
}
-static int cocoa_reconfig(struct MPGLContext *ctx)
+static void resize(struct ra_ctx *ctx)
{
- vo_cocoa_config_window(ctx->vo);
- return 0;
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
}
-static int cocoa_control(struct MPGLContext *ctx, int *events, int request,
- void *arg)
+static bool cocoa_reconfig(struct ra_ctx *ctx)
{
- return vo_cocoa_control(ctx->vo, events, request, arg);
+ vo_cocoa_config_window(ctx->vo);
+ resize(ctx);
+ return true;
}
-static void cocoa_swap_buffers(struct MPGLContext *ctx)
+static int cocoa_control(struct ra_ctx *ctx, int *events, int request,
+ void *arg)
{
- vo_cocoa_swap_buffers(ctx->vo);
- ctx->gl->Flush();
+ int ret = vo_cocoa_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
}
-const struct mpgl_driver mpgl_driver_cocoa = {
+const struct ra_ctx_fns ra_ctx_cocoa = {
+ .type = "opengl",
.name = "cocoa",
- .priv_size = sizeof(struct priv),
.init = cocoa_init,
.reconfig = cocoa_reconfig,
- .swap_buffers = cocoa_swap_buffers,
.control = cocoa_control,
.uninit = cocoa_uninit,
-}; \ No newline at end of file
+};
diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c
index e52fec4..6191309 100644
--- a/video/out/opengl/context_drm_egl.c
+++ b/video/out/opengl/context_drm_egl.c
@@ -25,22 +25,25 @@
#include <unistd.h>
#include <gbm.h>
+#include <drm_fourcc.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
-#include "context.h"
-#include "egl_helpers.h"
-#include "common/common.h"
+#include "libmpv/opengl_cb.h"
#include "video/out/drm_common.h"
+#include "common/common.h"
+
+#include "egl_helpers.h"
+#include "common.h"
+#include "context.h"
#define USE_MASTER 0
struct framebuffer
{
- struct gbm_bo *bo;
- int width, height;
int fd;
- int id;
+ uint32_t width, height;
+ uint32_t id;
};
struct gbm
@@ -59,6 +62,7 @@ struct egl
};
struct priv {
+ GL gl;
struct kms *kms;
drmEventContext ev;
@@ -66,43 +70,46 @@ struct priv {
struct egl egl;
struct gbm gbm;
- struct framebuffer fb;
+ struct framebuffer *fb;
+
+ uint32_t primary_plane_format;
bool active;
bool waiting_for_flip;
bool vt_switcher_active;
struct vt_switcher vt_switcher;
+
+ struct mpv_opengl_cb_drm_params drm_params;
};
-static bool init_egl(struct MPGLContext *ctx, int flags)
+static bool init_egl(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- MP_VERBOSE(ctx->vo, "Initializing EGL\n");
+ MP_VERBOSE(ctx, "Initializing EGL\n");
p->egl.display = eglGetDisplay(p->gbm.device);
if (p->egl.display == EGL_NO_DISPLAY) {
- MP_ERR(ctx->vo, "Failed to get EGL display.\n");
+ MP_ERR(ctx, "Failed to get EGL display.\n");
return false;
}
if (!eglInitialize(p->egl.display, NULL, NULL)) {
- MP_ERR(ctx->vo, "Failed to initialize EGL.\n");
+ MP_ERR(ctx, "Failed to initialize EGL.\n");
return false;
}
EGLConfig config;
- if (!mpegl_create_context(p->egl.display, ctx->vo->log, flags,
- &p->egl.context, &config))
- return -1;
- MP_VERBOSE(ctx->vo, "Initializing EGL surface\n");
+ if (!mpegl_create_context(ctx, p->egl.display, &p->egl.context, &config))
+ return false;
+ MP_VERBOSE(ctx, "Initializing EGL surface\n");
p->egl.surface
= eglCreateWindowSurface(p->egl.display, config, p->gbm.surface, NULL);
if (p->egl.surface == EGL_NO_SURFACE) {
- MP_ERR(ctx->vo, "Failed to create EGL surface.\n");
+ MP_ERR(ctx, "Failed to create EGL surface.\n");
return false;
}
return true;
}
-static bool init_gbm(struct MPGLContext *ctx)
+static bool init_gbm(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
MP_VERBOSE(ctx->vo, "Creating GBM device\n");
@@ -118,7 +125,7 @@ static bool init_gbm(struct MPGLContext *ctx)
p->gbm.device,
p->kms->mode.hdisplay,
p->kms->mode.vdisplay,
- GBM_BO_FORMAT_XRGB8888,
+ p->primary_plane_format, // drm_fourcc.h defs should be gbm-compatible
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!p->gbm.surface) {
MP_ERR(ctx->vo, "Failed to create GBM surface.\n");
@@ -135,46 +142,50 @@ static void framebuffer_destroy_callback(struct gbm_bo *bo, void *data)
}
}
-static void update_framebuffer_from_bo(
- const struct MPGLContext *ctx, struct gbm_bo *bo)
+static void update_framebuffer_from_bo(struct ra_ctx *ctx, struct gbm_bo *bo)
{
struct priv *p = ctx->priv;
- p->fb.bo = bo;
- p->fb.fd = p->kms->fd;
- p->fb.width = gbm_bo_get_width(bo);
- p->fb.height = gbm_bo_get_height(bo);
- int stride = gbm_bo_get_stride(bo);
- int handle = gbm_bo_get_handle(bo).u32;
-
- int ret = drmModeAddFB(p->kms->fd, p->fb.width, p->fb.height,
- 24, 32, stride, handle, &p->fb.id);
+ struct framebuffer *fb = gbm_bo_get_user_data(bo);
+ if (fb) {
+ p->fb = fb;
+ return;
+ }
+
+ fb = talloc_zero(ctx, struct framebuffer);
+ fb->fd = p->kms->fd;
+ fb->width = gbm_bo_get_width(bo);
+ fb->height = gbm_bo_get_height(bo);
+ uint32_t stride = gbm_bo_get_stride(bo);
+ uint32_t handle = gbm_bo_get_handle(bo).u32;
+
+ int ret = drmModeAddFB2(fb->fd, fb->width, fb->height,
+ p->primary_plane_format,
+ (uint32_t[4]){handle, 0, 0, 0},
+ (uint32_t[4]){stride, 0, 0, 0},
+ (uint32_t[4]){0, 0, 0, 0},
+ &fb->id, 0);
+
if (ret) {
MP_ERR(ctx->vo, "Failed to create framebuffer: %s\n", mp_strerror(errno));
}
- gbm_bo_set_user_data(bo, &p->fb, framebuffer_destroy_callback);
-}
-
-static void page_flipped(int fd, unsigned int frame, unsigned int sec,
- unsigned int usec, void *data)
-{
- struct priv *p = data;
- p->waiting_for_flip = false;
+ gbm_bo_set_user_data(bo, fb, framebuffer_destroy_callback);
+ p->fb = fb;
}
-static bool crtc_setup(struct MPGLContext *ctx)
+static bool crtc_setup(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->active)
return true;
p->old_crtc = drmModeGetCrtc(p->kms->fd, p->kms->crtc_id);
- int ret = drmModeSetCrtc(p->kms->fd, p->kms->crtc_id, p->fb.id,
+ int ret = drmModeSetCrtc(p->kms->fd, p->kms->crtc_id, p->fb->id,
0, 0, &p->kms->connector->connector_id, 1,
&p->kms->mode);
p->active = true;
return ret == 0;
}
-static void crtc_release(struct MPGLContext *ctx)
+static void crtc_release(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -204,7 +215,7 @@ static void crtc_release(struct MPGLContext *ctx)
static void release_vt(void *data)
{
- struct MPGLContext *ctx = data;
+ struct ra_ctx *ctx = data;
MP_VERBOSE(ctx->vo, "Releasing VT");
crtc_release(ctx);
if (USE_MASTER) {
@@ -221,7 +232,7 @@ static void release_vt(void *data)
static void acquire_vt(void *data)
{
- struct MPGLContext *ctx = data;
+ struct ra_ctx *ctx = data;
MP_VERBOSE(ctx->vo, "Acquiring VT");
if (USE_MASTER) {
struct priv *p = ctx->priv;
@@ -234,11 +245,78 @@ static void acquire_vt(void *data)
crtc_setup(ctx);
}
-static void drm_egl_uninit(MPGLContext *ctx)
+static bool drm_atomic_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
+{
+ struct priv *p = sw->ctx->priv;
+ if (p->kms->atomic_context) {
+ p->kms->atomic_context->request = drmModeAtomicAlloc();
+ p->drm_params.atomic_request = p->kms->atomic_context->request;
+ return ra_gl_ctx_start_frame(sw, out_fbo);
+ }
+ return false;
+}
+
+static const struct ra_swapchain_fns drm_atomic_swapchain = {
+ .start_frame = drm_atomic_egl_start_frame,
+};
+
+static void drm_egl_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- crtc_release(ctx);
+ struct drm_atomic_context *atomic_ctx = p->kms->atomic_context;
+ int ret;
+
+ eglSwapBuffers(p->egl.display, p->egl.surface);
+ p->gbm.next_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
+ p->waiting_for_flip = true;
+ update_framebuffer_from_bo(ctx, p->gbm.next_bo);
+
+ if (atomic_ctx) {
+ drm_object_set_property(atomic_ctx->request, atomic_ctx->primary_plane, "FB_ID", p->fb->id);
+ drm_object_set_property(atomic_ctx->request, atomic_ctx->primary_plane, "CRTC_ID", atomic_ctx->crtc->id);
+ drm_object_set_property(atomic_ctx->request, atomic_ctx->primary_plane, "ZPOS", 1);
+
+ ret = drmModeAtomicCommit(p->kms->fd, atomic_ctx->request,
+ DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, NULL);
+ if (ret)
+ MP_WARN(ctx->vo, "Failed to commit atomic request (%d)\n", ret);
+ } else {
+ ret = drmModePageFlip(p->kms->fd, p->kms->crtc_id, p->fb->id,
+ DRM_MODE_PAGE_FLIP_EVENT, p);
+ if (ret) {
+ MP_WARN(ctx->vo, "Failed to queue page flip: %s\n", mp_strerror(errno));
+ }
+ }
+
+ // poll page flip finish event
+ const int timeout_ms = 3000;
+ struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } };
+ poll(fds, 1, timeout_ms);
+ if (fds[0].revents & POLLIN) {
+ ret = drmHandleEvent(p->kms->fd, &p->ev);
+ if (ret != 0) {
+ MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
+ p->waiting_for_flip = false;
+ return;
+ }
+ }
+ p->waiting_for_flip = false;
+
+ if (atomic_ctx) {
+ drmModeAtomicFree(atomic_ctx->request);
+ p->drm_params.atomic_request = atomic_ctx->request = NULL;
+ }
+
+ gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo);
+ p->gbm.bo = p->gbm.next_bo;
+}
+
+static void drm_egl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+ crtc_release(ctx);
if (p->vt_switcher_active)
vt_switcher_destroy(&p->vt_switcher);
@@ -258,100 +336,146 @@ static void drm_egl_uninit(MPGLContext *ctx)
}
}
-static int drm_egl_init(struct MPGLContext *ctx, int flags)
+// If primary plane supports ARGB8888 we want to use that, but if it doesn't we
+// fall back on XRGB8888. If the driver does not support atomic there is no
+// particular reason to be using ARGB8888, so we fall back to XRGB8888 (another
+// reason is that we do not have the convenient atomic_ctx and its convenient
+// primary_plane field).
+static bool probe_primary_plane_format(struct ra_ctx *ctx)
{
- if (ctx->vo->probing) {
- MP_VERBOSE(ctx->vo, "DRM EGL backend can be activated only manually.\n");
- return -1;
- }
struct priv *p = ctx->priv;
- p->kms = NULL;
- p->old_crtc = NULL;
- p->gbm.surface = NULL;
- p->gbm.device = NULL;
- p->active = false;
- p->waiting_for_flip = false;
+ if (!p->kms->atomic_context) {
+ p->primary_plane_format = DRM_FORMAT_XRGB8888;
+ MP_VERBOSE(ctx->vo, "Not using DRM Atomic: Use DRM_FORMAT_XRGB8888 for primary plane.\n");
+ return true;
+ }
+
+ drmModePlane *drmplane =
+ drmModeGetPlane(p->kms->fd, p->kms->atomic_context->primary_plane->id);
+ bool have_argb8888 = false;
+ bool have_xrgb8888 = false;
+ bool result = false;
+ for (unsigned int i = 0; i < drmplane->count_formats; ++i) {
+ if (drmplane->formats[i] == DRM_FORMAT_ARGB8888) {
+ have_argb8888 = true;
+ } else if (drmplane->formats[i] == DRM_FORMAT_XRGB8888) {
+ have_xrgb8888 = true;
+ }
+ }
+
+ if (have_argb8888) {
+ p->primary_plane_format = DRM_FORMAT_ARGB8888;
+ MP_VERBOSE(ctx->vo, "DRM_FORMAT_ARGB8888 supported by primary plane.\n");
+ result = true;
+ } else if (have_xrgb8888) {
+ p->primary_plane_format = DRM_FORMAT_XRGB8888;
+ MP_VERBOSE(ctx->vo,
+ "DRM_FORMAT_ARGB8888 not supported by primary plane: "
+ "Falling back to DRM_FORMAT_XRGB8888.\n");
+ result = true;
+ }
+
+ drmModeFreePlane(drmplane);
+ return result;
+}
+
+static bool drm_egl_init(struct ra_ctx *ctx)
+{
+ if (ctx->opts.probing) {
+ MP_VERBOSE(ctx, "DRM EGL backend can be activated only manually.\n");
+ return false;
+ }
+
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
p->ev.version = DRM_EVENT_CONTEXT_VERSION;
- p->ev.page_flip_handler = page_flipped;
p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, ctx->vo->log);
if (p->vt_switcher_active) {
vt_switcher_acquire(&p->vt_switcher, acquire_vt, ctx);
vt_switcher_release(&p->vt_switcher, release_vt, ctx);
} else {
- MP_WARN(ctx->vo, "Failed to set up VT switcher. Terminal switching will be unavailable.\n");
+ MP_WARN(ctx, "Failed to set up VT switcher. Terminal switching will be unavailable.\n");
}
- MP_VERBOSE(ctx->vo, "Initializing KMS\n");
- p->kms = kms_create(ctx->vo->log, ctx->vo->opts->drm_connector_spec,
- ctx->vo->opts->drm_mode_id);
+ MP_VERBOSE(ctx, "Initializing KMS\n");
+ p->kms = kms_create(ctx->log, ctx->vo->opts->drm_opts->drm_connector_spec,
+ ctx->vo->opts->drm_opts->drm_mode_id,
+ ctx->vo->opts->drm_opts->drm_overlay_id);
if (!p->kms) {
- MP_ERR(ctx->vo, "Failed to create KMS.\n");
- return -1;
+ MP_ERR(ctx, "Failed to create KMS.\n");
+ return false;
+ }
+
+ if (!probe_primary_plane_format(ctx)) {
+ MP_ERR(ctx->vo, "No suitable format found on DRM primary plane.\n");
+ return false;
}
if (!init_gbm(ctx)) {
MP_ERR(ctx->vo, "Failed to setup GBM.\n");
- return -1;
+ return false;
}
- if (!init_egl(ctx, flags)) {
+ if (!init_egl(ctx)) {
MP_ERR(ctx->vo, "Failed to setup EGL.\n");
- return -1;
+ return false;
}
if (!eglMakeCurrent(p->egl.display, p->egl.surface, p->egl.surface,
p->egl.context)) {
MP_ERR(ctx->vo, "Failed to make context current.\n");
- return -1;
+ return false;
}
- mpegl_load_functions(ctx->gl, ctx->vo->log);
-
- ctx->native_display_type = "drm";
- ctx->native_display = (void *)(intptr_t)p->kms->fd;
-
+ mpegl_load_functions(&p->gl, ctx->vo->log);
// required by gbm_surface_lock_front_buffer
eglSwapBuffers(p->egl.display, p->egl.surface);
- MP_VERBOSE(ctx->vo, "Preparing framebuffer\n");
+ MP_VERBOSE(ctx, "Preparing framebuffer\n");
p->gbm.bo = gbm_surface_lock_front_buffer(p->gbm.surface);
if (!p->gbm.bo) {
- MP_ERR(ctx->vo, "Failed to lock GBM surface.\n");
- return -1;
+ MP_ERR(ctx, "Failed to lock GBM surface.\n");
+ return false;
}
update_framebuffer_from_bo(ctx, p->gbm.bo);
- if (!p->fb.id) {
- MP_ERR(ctx->vo, "Failed to create framebuffer.\n");
- return -1;
+ if (!p->fb || !p->fb->id) {
+ MP_ERR(ctx, "Failed to create framebuffer.\n");
+ return false;
}
if (!crtc_setup(ctx)) {
- MP_ERR(ctx->vo, "Failed to set CRTC for connector %u: %s\n",
+ MP_ERR(ctx, "Failed to set CRTC for connector %u: %s\n",
p->kms->connector->connector_id, mp_strerror(errno));
- return -1;
+ return false;
}
- return 0;
-}
+ p->drm_params.fd = p->kms->fd;
+ p->drm_params.crtc_id = p->kms->crtc_id;
+ if (p->kms->atomic_context)
+ p->drm_params.atomic_request = p->kms->atomic_context->request;
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = drm_egl_swap_buffers,
+ .native_display_type = "opengl-cb-drm-params",
+ .native_display = &p->drm_params,
+ .external_swapchain = p->kms->atomic_context ? &drm_atomic_swapchain :
+ NULL,
+ };
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ return false;
-static int drm_egl_init_deprecated(struct MPGLContext *ctx, int flags)
-{
- if (ctx->vo->probing)
- return -1;
- MP_WARN(ctx->vo, "'drm-egl' is deprecated, use 'drm' instead.\n");
- return drm_egl_init(ctx, flags);
+ return true;
}
-static int drm_egl_reconfig(struct MPGLContext *ctx)
+static bool drm_egl_reconfig(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- ctx->vo->dwidth = p->fb.width;
- ctx->vo->dheight = p->fb.height;
- return 0;
+ ctx->vo->dwidth = p->fb->width;
+ ctx->vo->dheight = p->fb->height;
+ ra_gl_ctx_resize(ctx->swapchain, p->fb->width, p->fb->height, 0);
+ return true;
}
-static int drm_egl_control(struct MPGLContext *ctx, int *events, int request,
+static int drm_egl_control(struct ra_ctx *ctx, int *events, int request,
void *arg)
{
struct priv *p = ctx->priv;
@@ -367,51 +491,11 @@ static int drm_egl_control(struct MPGLContext *ctx, int *events, int request,
return VO_NOTIMPL;
}
-static void drm_egl_swap_buffers(MPGLContext *ctx)
-{
- struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl.display, p->egl.surface);
- p->gbm.next_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
- p->waiting_for_flip = true;
- update_framebuffer_from_bo(ctx, p->gbm.next_bo);
- int ret = drmModePageFlip(p->kms->fd, p->kms->crtc_id, p->fb.id,
- DRM_MODE_PAGE_FLIP_EVENT, p);
- if (ret) {
- MP_WARN(ctx->vo, "Failed to queue page flip: %s\n", mp_strerror(errno));
- }
-
- // poll page flip finish event
- const int timeout_ms = 3000;
- struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } };
- poll(fds, 1, timeout_ms);
- if (fds[0].revents & POLLIN) {
- ret = drmHandleEvent(p->kms->fd, &p->ev);
- if (ret != 0) {
- MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
- return;
- }
- }
-
- gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo);
- p->gbm.bo = p->gbm.next_bo;
-}
-
-const struct mpgl_driver mpgl_driver_drm = {
+const struct ra_ctx_fns ra_ctx_drm_egl = {
+ .type = "opengl",
.name = "drm",
- .priv_size = sizeof(struct priv),
- .init = drm_egl_init,
- .reconfig = drm_egl_reconfig,
- .swap_buffers = drm_egl_swap_buffers,
- .control = drm_egl_control,
- .uninit = drm_egl_uninit,
-};
-
-const struct mpgl_driver mpgl_driver_drm_egl = {
- .name = "drm-egl",
- .priv_size = sizeof(struct priv),
- .init = drm_egl_init_deprecated,
.reconfig = drm_egl_reconfig,
- .swap_buffers = drm_egl_swap_buffers,
.control = drm_egl_control,
+ .init = drm_egl_init,
.uninit = drm_egl_uninit,
};
diff --git a/video/out/opengl/context_dxinterop.c b/video/out/opengl/context_dxinterop.c
index 507c150..85d84bf 100644
--- a/video/out/opengl/context_dxinterop.c
+++ b/video/out/opengl/context_dxinterop.c
@@ -22,6 +22,7 @@
#include "osdep/windows_utils.h"
#include "video/out/w32_common.h"
#include "context.h"
+#include "utils.h"
// For WGL_ACCESS_WRITE_DISCARD_NV, etc.
#include <GL/wglext.h>
@@ -35,6 +36,8 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#endif
struct priv {
+ GL gl;
+
HMODULE d3d9_dll;
HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D);
@@ -54,6 +57,7 @@ struct priv {
// OpenGL resources
GLuint texture;
+ GLuint main_fb;
// Did we lose the device?
bool lost_device;
@@ -63,7 +67,7 @@ struct priv {
int width, height, swapinterval;
};
-static __thread struct MPGLContext *current_ctx;
+static __thread struct ra_ctx *current_ctx;
static void pump_message_loop(void)
{
@@ -84,10 +88,11 @@ static void *w32gpa(const GLubyte *procName)
return GetProcAddress(oglmod, procName);
}
-static int os_ctx_create(struct MPGLContext *ctx)
+static int os_ctx_create(struct ra_ctx *ctx)
{
static const wchar_t os_wnd_class[] = L"mpv offscreen gl";
struct priv *p = ctx->priv;
+ GL *gl = &p->gl;
HGLRC legacy_context = NULL;
RegisterClassExW(&(WNDCLASSEXW) {
@@ -190,8 +195,8 @@ static int os_ctx_create(struct MPGLContext *ctx)
goto fail;
}
- mpgl_load_functions(ctx->gl, w32gpa, wgl_exts, ctx->vo->log);
- if (!(ctx->gl->mpgl_caps & MPGL_CAP_DXINTEROP)) {
+ mpgl_load_functions(gl, w32gpa, wgl_exts, ctx->vo->log);
+ if (!(gl->mpgl_caps & MPGL_CAP_DXINTEROP)) {
MP_FATAL(ctx->vo, "WGL_NV_DX_interop is not supported\n");
goto fail;
}
@@ -205,7 +210,7 @@ fail:
return -1;
}
-static void os_ctx_destroy(MPGLContext *ctx)
+static void os_ctx_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -219,10 +224,10 @@ static void os_ctx_destroy(MPGLContext *ctx)
DestroyWindow(p->os_wnd);
}
-static int d3d_size_dependent_create(MPGLContext *ctx)
+static int d3d_size_dependent_create(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- struct GL *gl = ctx->gl;
+ GL *gl = &p->gl;
HRESULT hr;
IDirect3DSwapChain9 *sw9;
@@ -294,7 +299,7 @@ static int d3d_size_dependent_create(MPGLContext *ctx)
return -1;
}
- gl->BindFramebuffer(GL_FRAMEBUFFER, ctx->main_fb);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, p->main_fb);
gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, p->texture, 0);
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -302,10 +307,10 @@ static int d3d_size_dependent_create(MPGLContext *ctx)
return 0;
}
-static void d3d_size_dependent_destroy(MPGLContext *ctx)
+static void d3d_size_dependent_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- struct GL *gl = ctx->gl;
+ GL *gl = &p->gl;
if (p->rtarget_h) {
gl->DXUnlockObjectsNV(p->device_h, 1, &p->rtarget_h);
@@ -321,7 +326,8 @@ static void d3d_size_dependent_destroy(MPGLContext *ctx)
SAFE_RELEASE(p->swapchain);
}
-static void fill_presentparams(MPGLContext *ctx, D3DPRESENT_PARAMETERS *pparams)
+static void fill_presentparams(struct ra_ctx *ctx,
+ D3DPRESENT_PARAMETERS *pparams)
{
struct priv *p = ctx->priv;
@@ -338,13 +344,9 @@ static void fill_presentparams(MPGLContext *ctx, D3DPRESENT_PARAMETERS *pparams)
.Windowed = TRUE,
.BackBufferWidth = ctx->vo->dwidth ? ctx->vo->dwidth : 1,
.BackBufferHeight = ctx->vo->dheight ? ctx->vo->dheight : 1,
- // The length of the backbuffer queue shouldn't affect latency because
- // swap_buffers() always uses the backbuffer at the head of the queue
- // and presents it immediately. MSDN says there is a performance
- // penalty for having a short backbuffer queue and this seems to be
- // true, at least on Nvidia, where less than four backbuffers causes
- // very high CPU usage. Use six to be safe.
- .BackBufferCount = 6,
+ // Add one frame for the backbuffer and one frame of "slack" to reduce
+ // contention with the window manager when acquiring the backbuffer
+ .BackBufferCount = ctx->opts.swapchain_depth + 2,
.SwapEffect = IsWindows7OrGreater() ? D3DSWAPEFFECT_FLIPEX : D3DSWAPEFFECT_FLIP,
// Automatically get the backbuffer format from the display format
.BackBufferFormat = D3DFMT_UNKNOWN,
@@ -353,10 +355,10 @@ static void fill_presentparams(MPGLContext *ctx, D3DPRESENT_PARAMETERS *pparams)
};
}
-static int d3d_create(MPGLContext *ctx)
+static int d3d_create(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- struct GL *gl = ctx->gl;
+ GL *gl = &p->gl;
HRESULT hr;
p->d3d9_dll = LoadLibraryW(L"d3d9.dll");
@@ -396,8 +398,7 @@ static int d3d_create(MPGLContext *ctx)
return -1;
}
- // mpv expects frames to be presented right after swap_buffers() returns
- IDirect3DDevice9Ex_SetMaximumFrameLatency(p->device, 1);
+ IDirect3DDevice9Ex_SetMaximumFrameLatency(p->device, ctx->opts.swapchain_depth);
// Register the Direct3D device with WGL_NV_dx_interop
p->device_h = gl->DXOpenDeviceNV(p->device);
@@ -410,10 +411,10 @@ static int d3d_create(MPGLContext *ctx)
return 0;
}
-static void d3d_destroy(MPGLContext *ctx)
+static void d3d_destroy(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- struct GL *gl = ctx->gl;
+ GL *gl = &p->gl;
if (p->device_h)
gl->DXCloseDeviceNV(p->device_h);
@@ -423,8 +424,9 @@ static void d3d_destroy(MPGLContext *ctx)
FreeLibrary(p->d3d9_dll);
}
-static void dxinterop_uninit(MPGLContext *ctx)
+static void dxgl_uninit(struct ra_ctx *ctx)
{
+ ra_gl_ctx_uninit(ctx);
d3d_size_dependent_destroy(ctx);
d3d_destroy(ctx);
os_ctx_destroy(ctx);
@@ -433,7 +435,7 @@ static void dxinterop_uninit(MPGLContext *ctx)
pump_message_loop();
}
-static void dxinterop_reset(struct MPGLContext *ctx)
+static void dxgl_reset(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
HRESULT hr;
@@ -468,18 +470,18 @@ static void dxinterop_reset(struct MPGLContext *ctx)
p->lost_device = false;
}
-static int GLAPIENTRY dxinterop_swap_interval(int interval)
+static int GLAPIENTRY dxgl_swap_interval(int interval)
{
if (!current_ctx)
return 0;
struct priv *p = current_ctx->priv;
p->requested_swapinterval = interval;
- dxinterop_reset(current_ctx);
+ dxgl_reset(current_ctx);
return 1;
}
-static void * GLAPIENTRY dxinterop_get_native_display(const char *name)
+static void * GLAPIENTRY dxgl_get_native_display(const char *name)
{
if (!current_ctx || !name)
return NULL;
@@ -493,60 +495,17 @@ static void * GLAPIENTRY dxinterop_get_native_display(const char *name)
return NULL;
}
-static int dxinterop_init(struct MPGLContext *ctx, int flags)
-{
- struct priv *p = ctx->priv;
- struct GL *gl = ctx->gl;
-
- p->requested_swapinterval = 1;
-
- if (!vo_w32_init(ctx->vo))
- goto fail;
- if (os_ctx_create(ctx) < 0)
- goto fail;
-
- // Create the shared framebuffer
- gl->GenFramebuffers(1, &ctx->main_fb);
-
- current_ctx = ctx;
- gl->SwapInterval = dxinterop_swap_interval;
- gl->MPGetNativeDisplay = dxinterop_get_native_display;
-
- if (d3d_create(ctx) < 0)
- goto fail;
- if (d3d_size_dependent_create(ctx) < 0)
- goto fail;
-
- // The OpenGL and Direct3D coordinate systems are flipped vertically
- // relative to each other. Flip the video during rendering so it can be
- // copied to the Direct3D backbuffer with a simple (and fast) StretchRect.
- ctx->flip_v = true;
-
- DwmEnableMMCSS(TRUE);
-
- return 0;
-fail:
- dxinterop_uninit(ctx);
- return -1;
-}
-
-static int dxinterop_reconfig(struct MPGLContext *ctx)
-{
- vo_w32_config(ctx->vo);
- return 0;
-}
-
-static void dxinterop_swap_buffers(MPGLContext *ctx)
+static void dxgl_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- struct GL *gl = ctx->gl;
+ GL *gl = &p->gl;
HRESULT hr;
pump_message_loop();
// If the device is still lost, try to reset it again
if (p->lost_device)
- dxinterop_reset(ctx);
+ dxgl_reset(ctx);
if (p->lost_device)
return;
@@ -571,7 +530,7 @@ static void dxinterop_swap_buffers(MPGLContext *ctx)
case D3DERR_DEVICEHUNG:
MP_VERBOSE(ctx->vo, "Direct3D device lost! Resetting.\n");
p->lost_device = true;
- dxinterop_reset(ctx);
+ dxgl_reset(ctx);
return;
default:
if (FAILED(hr))
@@ -584,21 +543,75 @@ static void dxinterop_swap_buffers(MPGLContext *ctx)
}
}
-static int dxinterop_control(MPGLContext *ctx, int *events, int request,
+static bool dxgl_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ GL *gl = &p->gl;
+
+ p->requested_swapinterval = 1;
+
+ if (!vo_w32_init(ctx->vo))
+ goto fail;
+ if (os_ctx_create(ctx) < 0)
+ goto fail;
+
+ // Create the shared framebuffer
+ gl->GenFramebuffers(1, &p->main_fb);
+
+ current_ctx = ctx;
+ gl->SwapInterval = dxgl_swap_interval;
+ gl->MPGetNativeDisplay = dxgl_get_native_display;
+
+ if (d3d_create(ctx) < 0)
+ goto fail;
+ if (d3d_size_dependent_create(ctx) < 0)
+ goto fail;
+
+ static const struct ra_swapchain_fns empty_swapchain_fns = {0};
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = dxgl_swap_buffers,
+ .flipped = true,
+ .external_swapchain = &empty_swapchain_fns,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto fail;
+
+ DwmEnableMMCSS(TRUE);
+ return true;
+fail:
+ dxgl_uninit(ctx);
+ return false;
+}
+
+static void resize(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ dxgl_reset(ctx);
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, p->main_fb);
+}
+
+static bool dxgl_reconfig(struct ra_ctx *ctx)
+{
+ vo_w32_config(ctx->vo);
+ resize(ctx);
+ return true;
+}
+
+static int dxgl_control(struct ra_ctx *ctx, int *events, int request,
void *arg)
{
- int r = vo_w32_control(ctx->vo, events, request, arg);
+ int ret = vo_w32_control(ctx->vo, events, request, arg);
if (*events & VO_EVENT_RESIZE)
- dxinterop_reset(ctx);
- return r;
+ resize(ctx);
+ return ret;
}
-const struct mpgl_driver mpgl_driver_dxinterop = {
+const struct ra_ctx_fns ra_ctx_dxgl = {
+ .type = "opengl",
.name = "dxinterop",
- .priv_size = sizeof(struct priv),
- .init = dxinterop_init,
- .reconfig = dxinterop_reconfig,
- .swap_buffers = dxinterop_swap_buffers,
- .control = dxinterop_control,
- .uninit = dxinterop_uninit,
+ .init = dxgl_init,
+ .reconfig = dxgl_reconfig,
+ .control = dxgl_control,
+ .uninit = dxgl_uninit,
};
diff --git a/video/out/opengl/context_x11.c b/video/out/opengl/context_glx.c
index 4d8dac1..462f2cf 100644
--- a/video/out/opengl/context_x11.c
+++ b/video/out/opengl/context_glx.c
@@ -39,43 +39,46 @@
#include "video/out/x11_common.h"
#include "context.h"
+#include "utils.h"
-struct glx_context {
+struct priv {
+ GL gl;
XVisualInfo *vinfo;
GLXContext context;
GLXFBConfig fbc;
};
-static void glx_uninit(MPGLContext *ctx)
+static void glx_uninit(struct ra_ctx *ctx)
{
- struct glx_context *glx_ctx = ctx->priv;
- if (glx_ctx->vinfo)
- XFree(glx_ctx->vinfo);
- if (glx_ctx->context) {
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
+ if (p->vinfo)
+ XFree(p->vinfo);
+ if (p->context) {
Display *display = ctx->vo->x11->display;
glXMakeCurrent(display, None, NULL);
- glXDestroyContext(display, glx_ctx->context);
+ glXDestroyContext(display, p->context);
}
+
vo_x11_uninit(ctx->vo);
}
-static bool create_context_x11_old(struct MPGLContext *ctx)
+static bool create_context_x11_old(struct ra_ctx *ctx, GL *gl)
{
- struct glx_context *glx_ctx = ctx->priv;
+ struct priv *p = ctx->priv;
Display *display = ctx->vo->x11->display;
struct vo *vo = ctx->vo;
- GL *gl = ctx->gl;
- if (glx_ctx->context)
+ if (p->context)
return true;
- if (!glx_ctx->vinfo) {
+ if (!p->vinfo) {
MP_FATAL(vo, "Can't create a legacy GLX context without X visual\n");
return false;
}
- GLXContext new_context = glXCreateContext(display, glx_ctx->vinfo, NULL,
- True);
+ GLXContext new_context = glXCreateContext(display, p->vinfo, NULL, True);
if (!new_context) {
MP_FATAL(vo, "Could not create GLX context!\n");
return false;
@@ -91,7 +94,7 @@ static bool create_context_x11_old(struct MPGLContext *ctx)
mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
- glx_ctx->context = new_context;
+ p->context = new_context;
return true;
}
@@ -99,15 +102,18 @@ static bool create_context_x11_old(struct MPGLContext *ctx)
typedef GLXContext (*glXCreateContextAttribsARBProc)
(Display*, GLXFBConfig, GLXContext, Bool, const int*);
-static bool create_context_x11_gl3(struct MPGLContext *ctx, int vo_flags,
- int gl_version, bool es)
+static bool create_context_x11_gl3(struct ra_ctx *ctx, GL *gl, int gl_version,
+ bool es)
{
- struct glx_context *glx_ctx = ctx->priv;
+ struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
- if (glx_ctx->context)
+ if (p->context)
return true;
+ if (!ra_gl_ctx_test_version(ctx, gl_version, es))
+ return false;
+
glXCreateContextAttribsARBProc glXCreateContextAttribsARB =
(glXCreateContextAttribsARBProc)
glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
@@ -120,7 +126,7 @@ static bool create_context_x11_gl3(struct MPGLContext *ctx, int vo_flags,
return false;
}
- int ctx_flags = vo_flags & VOFLAG_GL_DEBUG ? GLX_CONTEXT_DEBUG_BIT_ARB : 0;
+ int ctx_flags = ctx->opts.debug ? GLX_CONTEXT_DEBUG_BIT_ARB : 0;
int profile_mask = GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
if (es) {
@@ -138,7 +144,7 @@ static bool create_context_x11_gl3(struct MPGLContext *ctx, int vo_flags,
};
vo_x11_silence_xlib(1);
GLXContext context = glXCreateContextAttribsARB(vo->x11->display,
- glx_ctx->fbc, 0, True,
+ p->fbc, 0, True,
context_attribs);
vo_x11_silence_xlib(-1);
if (!context)
@@ -151,9 +157,9 @@ static bool create_context_x11_gl3(struct MPGLContext *ctx, int vo_flags,
return false;
}
- glx_ctx->context = context;
+ p->context = context;
- mpgl_load_functions(ctx->gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
+ mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
return true;
}
@@ -162,7 +168,7 @@ static bool create_context_x11_gl3(struct MPGLContext *ctx, int vo_flags,
// http://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)
// but also uses some of the old code.
-static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs, int flags)
+static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs, bool alpha)
{
int fbcount;
GLXFBConfig *fbc = glXChooseFBConfig(vo->x11->display, vo->x11->screen,
@@ -173,7 +179,7 @@ static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs, int flags
// The list in fbc is sorted (so that the first element is the best).
GLXFBConfig fbconfig = fbcount > 0 ? fbc[0] : NULL;
- if (flags & VOFLAG_ALPHA) {
+ if (alpha) {
for (int n = 0; n < fbcount; n++) {
XVisualInfo *v = glXGetVisualFromFBConfig(vo->x11->display, fbc[n]);
if (v) {
@@ -202,10 +208,16 @@ static void set_glx_attrib(int *attribs, int name, int value)
}
}
-static int glx_init(struct MPGLContext *ctx, int flags)
+static void glx_swap_buffers(struct ra_ctx *ctx)
+{
+ glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
+}
+
+static bool glx_init(struct ra_ctx *ctx)
{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
struct vo *vo = ctx->vo;
- struct glx_context *glx_ctx = ctx->priv;
+ GL *gl = &p->gl;
if (!vo_x11_init(ctx->vo))
goto uninit;
@@ -213,12 +225,12 @@ static int glx_init(struct MPGLContext *ctx, int flags)
int glx_major, glx_minor;
if (!glXQueryVersion(vo->x11->display, &glx_major, &glx_minor)) {
- MP_ERR(vo, "GLX not found.\n");
+ MP_ERR(ctx, "GLX not found.\n");
goto uninit;
}
// FBConfigs were added in GLX version 1.3.
if (MPGL_VER(glx_major, glx_minor) < MPGL_VER(1, 3)) {
- MP_ERR(vo, "GLX version older than 1.3.\n");
+ MP_ERR(ctx, "GLX version older than 1.3.\n");
goto uninit;
}
@@ -233,126 +245,132 @@ static int glx_init(struct MPGLContext *ctx, int flags)
None
};
GLXFBConfig fbc = NULL;
- if (flags & VOFLAG_ALPHA) {
+ if (ctx->opts.want_alpha) {
set_glx_attrib(glx_attribs, GLX_ALPHA_SIZE, 1);
- fbc = select_fb_config(vo, glx_attribs, flags);
- if (!fbc) {
+ fbc = select_fb_config(vo, glx_attribs, true);
+ if (!fbc)
set_glx_attrib(glx_attribs, GLX_ALPHA_SIZE, 0);
- flags &= ~VOFLAG_ALPHA;
- }
}
if (!fbc)
- fbc = select_fb_config(vo, glx_attribs, flags);
+ fbc = select_fb_config(vo, glx_attribs, false);
if (!fbc) {
- MP_ERR(vo, "no GLX support present\n");
+ MP_ERR(ctx, "no GLX support present\n");
goto uninit;
}
int fbid = -1;
if (!glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_FBCONFIG_ID, &fbid))
- MP_VERBOSE(vo, "GLX chose FB config with ID 0x%x\n", fbid);
+ MP_VERBOSE(ctx, "GLX chose FB config with ID 0x%x\n", fbid);
- glx_ctx->fbc = fbc;
- glx_ctx->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc);
- if (glx_ctx->vinfo) {
- MP_VERBOSE(vo, "GLX chose visual with ID 0x%x\n",
- (int)glx_ctx->vinfo->visualid);
+ p->fbc = fbc;
+ p->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc);
+ if (p->vinfo) {
+ MP_VERBOSE(ctx, "GLX chose visual with ID 0x%x\n",
+ (int)p->vinfo->visualid);
} else {
- MP_WARN(vo, "Selected GLX FB config has no associated X visual\n");
+ MP_WARN(ctx, "Selected GLX FB config has no associated X visual\n");
}
- if (!vo_x11_create_vo_window(vo, glx_ctx->vinfo, "gl"))
+ if (!vo_x11_create_vo_window(vo, p->vinfo, "gl"))
goto uninit;
bool success = false;
- if (!(flags & VOFLAG_GLES)) {
- for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
- int version = mpgl_preferred_gl_versions[n];
- MP_VERBOSE(vo, "Creating OpenGL %d.%d context...\n",
- MPGL_VER_P(version));
- if (version >= 300) {
- success = create_context_x11_gl3(ctx, flags, version, false);
- } else {
- success = create_context_x11_old(ctx);
- }
- if (success)
- break;
+ for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
+ int version = mpgl_preferred_gl_versions[n];
+ MP_VERBOSE(ctx, "Creating OpenGL %d.%d context...\n",
+ MPGL_VER_P(version));
+ if (version >= 300) {
+ success = create_context_x11_gl3(ctx, gl, version, false);
+ } else {
+ success = create_context_x11_old(ctx, gl);
}
+ if (success)
+ break;
}
- if (!success) // try ES
- success = create_context_x11_gl3(ctx, flags, 200, true);
- if (success && !glXIsDirect(vo->x11->display, glx_ctx->context))
- ctx->gl->mpgl_caps |= MPGL_CAP_SW;
+ if (!success) // try again for GLES
+ success = create_context_x11_gl3(ctx, gl, 200, true);
+ if (success && !glXIsDirect(vo->x11->display, p->context))
+ gl->mpgl_caps |= MPGL_CAP_SW;
if (!success)
goto uninit;
- return 0;
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = glx_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto uninit;
+
+ return true;
uninit:
glx_uninit(ctx);
- return -1;
+ return false;
}
-static int glx_init_probe(struct MPGLContext *ctx, int flags)
+static bool glx_init_probe(struct ra_ctx *ctx)
{
- int r = glx_init(ctx, flags);
- if (r >= 0) {
- if (!(ctx->gl->mpgl_caps & MPGL_CAP_VDPAU)) {
- MP_VERBOSE(ctx->vo, "No vdpau support found - probing more things.\n");
- glx_uninit(ctx);
- r = -1;
- }
+ if (!glx_init(ctx))
+ return false;
+
+ struct priv *p = ctx->priv;
+ if (!(p->gl.mpgl_caps & MPGL_CAP_VDPAU)) {
+ MP_VERBOSE(ctx, "No vdpau support found - probing more things.\n");
+ glx_uninit(ctx);
+ return false;
}
- return r;
+
+ return true;
}
-static int glx_reconfig(struct MPGLContext *ctx)
+static void resize(struct ra_ctx *ctx)
{
- vo_x11_config_vo_window(ctx->vo);
- return 0;
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
}
-static int glx_control(struct MPGLContext *ctx, int *events, int request,
- void *arg)
+static bool glx_reconfig(struct ra_ctx *ctx)
{
- return vo_x11_control(ctx->vo, events, request, arg);
+ vo_x11_config_vo_window(ctx->vo);
+ resize(ctx);
+ return true;
}
-static void glx_swap_buffers(struct MPGLContext *ctx)
+static int glx_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
- glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
+ int ret = vo_x11_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
}
-static void glx_wakeup(struct MPGLContext *ctx)
+static void glx_wakeup(struct ra_ctx *ctx)
{
vo_x11_wakeup(ctx->vo);
}
-static void glx_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+static void glx_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
{
vo_x11_wait_events(ctx->vo, until_time_us);
}
-const struct mpgl_driver mpgl_driver_x11 = {
+const struct ra_ctx_fns ra_ctx_glx = {
+ .type = "opengl",
.name = "x11",
- .priv_size = sizeof(struct glx_context),
- .init = glx_init,
.reconfig = glx_reconfig,
- .swap_buffers = glx_swap_buffers,
.control = glx_control,
.wakeup = glx_wakeup,
.wait_events = glx_wait_events,
+ .init = glx_init,
.uninit = glx_uninit,
};
-const struct mpgl_driver mpgl_driver_x11_probe = {
+const struct ra_ctx_fns ra_ctx_glx_probe = {
+ .type = "opengl",
.name = "x11probe",
- .priv_size = sizeof(struct glx_context),
- .init = glx_init_probe,
.reconfig = glx_reconfig,
- .swap_buffers = glx_swap_buffers,
.control = glx_control,
.wakeup = glx_wakeup,
.wait_events = glx_wait_events,
+ .init = glx_init_probe,
.uninit = glx_uninit,
};
diff --git a/video/out/opengl/context_mali_fbdev.c b/video/out/opengl/context_mali_fbdev.c
index 66daa7f..8576e53 100644
--- a/video/out/opengl/context_mali_fbdev.c
+++ b/video/out/opengl/context_mali_fbdev.c
@@ -50,8 +50,7 @@ static bool get_fbdev_size(int *w, int *h)
}
struct priv {
- struct mp_log *log;
- struct GL *gl;
+ struct GL gl;
EGLDisplay egl_display;
EGLConfig egl_config;
EGLContext egl_context;
@@ -60,9 +59,10 @@ struct priv {
int w, h;
};
-static void mali_uninit(struct MPGLContext *ctx)
+static void mali_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
if (p->egl_surface) {
eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
@@ -74,25 +74,29 @@ static void mali_uninit(struct MPGLContext *ctx)
eglReleaseThread();
}
-static int mali_init(struct MPGLContext *ctx, int flags)
+static void mali_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- p->log = ctx->vo->log;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+}
+
+static bool mali_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
if (!get_fbdev_size(&p->w, &p->h)) {
- MP_FATAL(p, "Could not get fbdev size.\n");
+ MP_FATAL(ctx, "Could not get fbdev size.\n");
goto fail;
}
p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!eglInitialize(p->egl_display, NULL, NULL)) {
- MP_FATAL(p, "EGL failed to initialize.\n");
+ MP_FATAL(ctx, "EGL failed to initialize.\n");
goto fail;
}
EGLConfig config;
- if (!mpegl_create_context(p->egl_display, p->log, flags, &p->egl_context,
- &config))
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &config))
goto fail;
p->egl_window = (struct fbdev_window){
@@ -104,53 +108,51 @@ static int mali_init(struct MPGLContext *ctx, int flags)
(EGLNativeWindowType)&p->egl_window, NULL);
if (p->egl_surface == EGL_NO_SURFACE) {
- MP_FATAL(p, "Could not create EGL surface!\n");
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
goto fail;
}
if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
p->egl_context))
{
- MP_FATAL(p, "Failed to set context!\n");
+ MP_FATAL(ctx, "Failed to set context!\n");
goto fail;
}
- ctx->gl = talloc_zero(ctx, GL);
+ mpegl_load_functions(&p->gl, ctx->log);
- mpegl_load_functions(ctx->gl, p->log);
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = mali_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto fail;
- return 0;
+ return true;
fail:
mali_uninit(ctx);
- return -1;
+ return false;
}
-static int mali_reconfig(struct MPGLContext *ctx)
+static bool mali_reconfig(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
ctx->vo->dwidth = p->w;
ctx->vo->dheight = p->h;
- return 0;
+ ra_gl_ctx_resize(ctx->swapchain, p->w, p->h, 0);
}
-static void mali_swap_buffers(MPGLContext *ctx)
-{
- struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl_display, p->egl_surface);
-}
-
-static int mali_control(MPGLContext *ctx, int *events, int request, void *arg)
+static int mali_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
return VO_NOTIMPL;
}
-const struct mpgl_driver mpgl_driver_mali = {
+const struct ra_ctx_fns ra_ctx_mali_fbdev = {
+ .type = "opengl",
.name = "mali-fbdev",
- .priv_size = sizeof(struct priv),
- .init = mali_init,
.reconfig = mali_reconfig,
- .swap_buffers = mali_swap_buffers,
.control = mali_control,
+ .init = mali_init,
.uninit = mali_uninit,
};
diff --git a/video/out/opengl/context_rpi.c b/video/out/opengl/context_rpi.c
index e79622b..8b447d0 100644
--- a/video/out/opengl/context_rpi.c
+++ b/video/out/opengl/context_rpi.c
@@ -30,7 +30,7 @@
#include "egl_helpers.h"
struct priv {
- struct mp_log *log;
+ struct GL gl;
DISPMANX_DISPLAY_HANDLE_T display;
DISPMANX_ELEMENT_HANDLE_T window;
DISPMANX_UPDATE_HANDLE_T update;
@@ -49,13 +49,13 @@ struct priv {
static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1,
uint32_t param2)
{
- struct MPGLContext *ctx = callback_data;
+ struct ra_ctx *ctx = callback_data;
struct priv *p = ctx->priv;
atomic_store(&p->reload_display, true);
vo_wakeup(ctx->vo);
}
-static void destroy_dispmanx(struct MPGLContext *ctx)
+static void destroy_dispmanx(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -77,9 +77,10 @@ static void destroy_dispmanx(struct MPGLContext *ctx)
p->update = 0;
}
-static void rpi_uninit(MPGLContext *ctx)
+static void rpi_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
vc_tv_unregister_callback_full(tv_callback, ctx);
@@ -92,26 +93,26 @@ static void rpi_uninit(MPGLContext *ctx)
p->egl_display = EGL_NO_DISPLAY;
}
-static int recreate_dispmanx(struct MPGLContext *ctx)
+static bool recreate_dispmanx(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
int display_nr = 0;
int layer = 0;
- MP_VERBOSE(ctx->vo, "Recreating DISPMANX state...\n");
+ MP_VERBOSE(ctx, "Recreating DISPMANX state...\n");
destroy_dispmanx(ctx);
p->display = vc_dispmanx_display_open(display_nr);
p->update = vc_dispmanx_update_start(0);
if (!p->display || !p->update) {
- MP_FATAL(ctx->vo, "Could not get DISPMANX objects.\n");
+ MP_FATAL(ctx, "Could not get DISPMANX objects.\n");
goto fail;
}
uint32_t dispw, disph;
if (graphics_get_display_size(0, &dispw, &disph) < 0) {
- MP_FATAL(ctx->vo, "Could not get display size.\n");
+ MP_FATAL(ctx, "Could not get display size.\n");
goto fail;
}
p->w = dispw;
@@ -145,7 +146,7 @@ static int recreate_dispmanx(struct MPGLContext *ctx)
&src, DISPMANX_PROTECTION_NONE, &alpha,
0, 0);
if (!p->window) {
- MP_FATAL(ctx->vo, "Could not add DISPMANX element.\n");
+ MP_FATAL(ctx, "Could not add DISPMANX element.\n");
goto fail;
}
@@ -161,14 +162,14 @@ static int recreate_dispmanx(struct MPGLContext *ctx)
&p->egl_window, NULL);
if (p->egl_surface == EGL_NO_SURFACE) {
- MP_FATAL(p, "Could not create EGL surface!\n");
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
goto fail;
}
if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
p->egl_context))
{
- MP_FATAL(p, "Failed to set context!\n");
+ MP_FATAL(ctx, "Failed to set context!\n");
goto fail;
}
@@ -197,21 +198,27 @@ static int recreate_dispmanx(struct MPGLContext *ctx)
ctx->vo->dwidth = p->w;
ctx->vo->dheight = p->h;
+ ra_gl_ctx_resize(ctx->swapchain, p->w, p->h, 0);
ctx->vo->want_redraw = true;
vo_event(ctx->vo, VO_EVENT_WIN_STATE);
- return 0;
+ return true;
fail:
destroy_dispmanx(ctx);
- return -1;
+ return false;
}
-static int rpi_init(struct MPGLContext *ctx, int flags)
+static void rpi_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
- p->log = ctx->vo->log;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+}
+
+static bool rpi_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
bcm_host_init();
@@ -219,43 +226,40 @@ static int rpi_init(struct MPGLContext *ctx, int flags)
p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!eglInitialize(p->egl_display, NULL, NULL)) {
- MP_FATAL(p, "EGL failed to initialize.\n");
+ MP_FATAL(ctx, "EGL failed to initialize.\n");
goto fail;
}
- if (!mpegl_create_context(p->egl_display, p->log, 0, &p->egl_context,
- &p->egl_config))
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config))
goto fail;
if (recreate_dispmanx(ctx) < 0)
goto fail;
- ctx->gl = talloc_zero(ctx, GL);
+ mpegl_load_functions(&p->gl, ctx->log);
- mpegl_load_functions(ctx->gl, p->log);
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = rpi_swap_buffers,
+ .native_display_type = "MPV_RPI_WINDOW",
+ .native_display = p->win_params,
+ };
- ctx->native_display_type = "MPV_RPI_WINDOW";
- ctx->native_display = p->win_params;
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto fail;
- return 0;
+ return true;
fail:
rpi_uninit(ctx);
- return -1;
+ return false;
}
-static int rpi_reconfig(struct MPGLContext *ctx)
+static bool rpi_reconfig(struct ra_ctx *ctx)
{
return recreate_dispmanx(ctx);
}
-static void rpi_swap_buffers(MPGLContext *ctx)
-{
- struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl_display, p->egl_surface);
-}
-
-static struct mp_image *take_screenshot(struct MPGLContext *ctx)
+static struct mp_image *take_screenshot(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -289,21 +293,20 @@ fail:
return NULL;
}
-
-static int rpi_control(MPGLContext *ctx, int *events, int request, void *arg)
+static int rpi_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
struct priv *p = ctx->priv;
switch (request) {
case VOCTRL_SCREENSHOT_WIN:
*(struct mp_image **)arg = take_screenshot(ctx);
- return true;
+ return VO_TRUE;
case VOCTRL_FULLSCREEN:
recreate_dispmanx(ctx);
return VO_TRUE;
case VOCTRL_CHECK_EVENTS:
if (atomic_fetch_and(&p->reload_display, 0)) {
- MP_WARN(ctx->vo, "Recovering from display mode switch...\n");
+ MP_WARN(ctx, "Recovering from display mode switch...\n");
recreate_dispmanx(ctx);
}
return VO_TRUE;
@@ -315,12 +318,11 @@ static int rpi_control(MPGLContext *ctx, int *events, int request, void *arg)
return VO_NOTIMPL;
}
-const struct mpgl_driver mpgl_driver_rpi = {
+const struct ra_ctx_fns ra_ctx_rpi = {
+ .type = "opengl",
.name = "rpi",
- .priv_size = sizeof(struct priv),
- .init = rpi_init,
.reconfig = rpi_reconfig,
- .swap_buffers = rpi_swap_buffers,
.control = rpi_control,
+ .init = rpi_init,
.uninit = rpi_uninit,
-}; \ No newline at end of file
+};
diff --git a/video/out/opengl/context_vdpau.c b/video/out/opengl/context_vdpau.c
index 40d21ab..e989414 100644
--- a/video/out/opengl/context_vdpau.c
+++ b/video/out/opengl/context_vdpau.c
@@ -26,8 +26,6 @@
// follow it. I'm not sure about the original nvidia headers.
#define BRAINDEATH(x) ((void *)(uintptr_t)(x))
-#define NUM_SURFACES 4
-
struct surface {
int w, h;
VdpOutputSurface surface;
@@ -39,21 +37,22 @@ struct surface {
};
struct priv {
+ GL gl;
GLXContext context;
struct mp_vdpau_ctx *vdp;
VdpPresentationQueueTarget vdp_target;
VdpPresentationQueue vdp_queue;
+ struct surface *surfaces;
int num_surfaces;
- struct surface surfaces[NUM_SURFACES];
- int current_surface;
+ int idx_surfaces;
};
typedef GLXContext (*glXCreateContextAttribsARBProc)
(Display*, GLXFBConfig, GLXContext, Bool, const int*);
-static bool create_context_x11(struct MPGLContext *ctx, int vo_flags)
+static bool create_context_x11(struct ra_ctx *ctx)
{
- struct priv *glx_ctx = ctx->priv;
+ struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
int glx_major, glx_minor;
@@ -62,6 +61,9 @@ static bool create_context_x11(struct MPGLContext *ctx, int vo_flags)
return false;
}
+ if (!ra_gl_ctx_test_version(ctx, MPGL_VER(glx_major, glx_minor), false))
+ return false;
+
int glx_attribs[] = {
GLX_X_RENDERABLE, True,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
@@ -96,7 +98,7 @@ static bool create_context_x11(struct MPGLContext *ctx, int vo_flags)
return false;
}
- int ctx_flags = vo_flags & VOFLAG_GL_DEBUG ? GLX_CONTEXT_DEBUG_BIT_ARB : 0;
+ int ctx_flags = ctx->opts.debug ? GLX_CONTEXT_DEBUG_BIT_ARB : 0;
int context_attribs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
@@ -117,19 +119,20 @@ static bool create_context_x11(struct MPGLContext *ctx, int vo_flags)
return false;
}
- glx_ctx->context = context;
- mpgl_load_functions(ctx->gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
+ p->context = context;
+ mpgl_load_functions(&p->gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
return true;
}
-static int create_vdpau_objects(struct MPGLContext *ctx)
+static int create_vdpau_objects(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ struct GL *gl = &p->gl;
VdpDevice dev = p->vdp->vdp_device;
struct vdp_functions *vdp = &p->vdp->vdp;
VdpStatus vdp_st;
- ctx->gl->VDPAUInitNV(BRAINDEATH(dev), p->vdp->get_proc_address);
+ gl->VDPAUInitNV(BRAINDEATH(dev), p->vdp->get_proc_address);
vdp_st = vdp->presentation_queue_target_create_x11(dev, ctx->vo->x11->window,
&p->vdp_target);
@@ -141,13 +144,13 @@ static int create_vdpau_objects(struct MPGLContext *ctx)
return 0;
}
-static void destroy_vdpau_surface(struct MPGLContext *ctx,
+static void destroy_vdpau_surface(struct ra_ctx *ctx,
struct surface *surface)
{
struct priv *p = ctx->priv;
struct vdp_functions *vdp = &p->vdp->vdp;
VdpStatus vdp_st;
- GL *gl = ctx->gl;
+ GL *gl = &p->gl;
if (surface->mapped)
gl->VDPAUUnmapSurfacesNV(1, &surface->registered);
@@ -168,14 +171,14 @@ static void destroy_vdpau_surface(struct MPGLContext *ctx,
};
}
-static int recreate_vdpau_surface(struct MPGLContext *ctx,
- struct surface *surface)
+static bool recreate_vdpau_surface(struct ra_ctx *ctx,
+ struct surface *surface)
{
struct priv *p = ctx->priv;
VdpDevice dev = p->vdp->vdp_device;
struct vdp_functions *vdp = &p->vdp->vdp;
VdpStatus vdp_st;
- GL *gl = ctx->gl;
+ GL *gl = &p->gl;
destroy_vdpau_surface(ctx, surface);
@@ -219,16 +222,37 @@ static int recreate_vdpau_surface(struct MPGLContext *ctx,
gl->VDPAUUnmapSurfacesNV(1, &surface->registered);
surface->mapped = false;
- return 0;
+ return true;
error:
destroy_vdpau_surface(ctx, surface);
- return -1;
+ return false;
+}
+
+static void vdpau_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vdp_functions *vdp = &p->vdp->vdp;
+ VdpStatus vdp_st;
+
+ // This is the *next* surface we will be rendering to. By delaying the
+ // block_until_idle, we're essentially allowing p->num_surfaces - 1
+ // in-flight surfaces, plus the one currently visible surface.
+ struct surface *surf = &p->surfaces[p->idx_surfaces];
+ if (surf->surface == VDP_INVALID_HANDLE)
+ return;
+
+ VdpTime prev_vsync_time;
+ vdp_st = vdp->presentation_queue_block_until_surface_idle(p->vdp_queue,
+ surf->surface,
+ &prev_vsync_time);
+ CHECK_VDP_WARNING(ctx, "waiting for surface failed");
}
-static void glx_uninit(MPGLContext *ctx)
+static void vdpau_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
if (p->vdp) {
struct vdp_functions *vdp = &p->vdp->vdp;
@@ -259,10 +283,12 @@ static void glx_uninit(MPGLContext *ctx)
vo_x11_uninit(ctx->vo);
}
-static int glx_init(struct MPGLContext *ctx, int flags)
+static const struct ra_swapchain_fns vdpau_swapchain;
+
+static bool vdpau_init(struct ra_ctx *ctx)
{
struct vo *vo = ctx->vo;
- struct priv *p = ctx->priv;
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
p->vdp_queue = VDP_INVALID_HANDLE;
p->vdp_target = VDP_INVALID_HANDLE;
@@ -280,110 +306,112 @@ static int glx_init(struct MPGLContext *ctx, int flags)
if (!vo_x11_create_vo_window(vo, NULL, "vdpauglx"))
goto uninit;
- if (!create_context_x11(ctx, flags))
+ if (!create_context_x11(ctx))
goto uninit;
- if (!(ctx->gl->mpgl_caps & MPGL_CAP_VDPAU))
+ if (!(p->gl.mpgl_caps & MPGL_CAP_VDPAU))
goto uninit;
if (create_vdpau_objects(ctx) < 0)
goto uninit;
- p->num_surfaces = NUM_SURFACES;
+ p->num_surfaces = ctx->opts.swapchain_depth + 1; // +1 for the visible image
+ p->surfaces = talloc_zero_array(p, struct surface, p->num_surfaces);
for (int n = 0; n < p->num_surfaces; n++)
p->surfaces[n].surface = VDP_INVALID_HANDLE;
- ctx->flip_v = true;
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = vdpau_swap_buffers,
+ .external_swapchain = &vdpau_swapchain,
+ .flipped = true,
+ };
- return 0;
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto uninit;
+
+ return true;
uninit:
- glx_uninit(ctx);
- return -1;
+ vdpau_uninit(ctx);
+ return false;
}
-static int glx_reconfig(struct MPGLContext *ctx)
+static bool vdpau_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
- vo_x11_config_vo_window(ctx->vo);
- return 0;
-}
+ struct priv *p = sw->ctx->priv;
+ struct vo *vo = sw->ctx->vo;
+ GL *gl = &p->gl;
+
+ struct surface *surf = &p->surfaces[p->idx_surfaces];
+ if (surf->w != vo->dwidth || surf->h != vo->dheight ||
+ surf->surface == VDP_INVALID_HANDLE)
+ {
+ if (!recreate_vdpau_surface(sw->ctx, surf))
+ return NULL;
+ }
-static int glx_control(struct MPGLContext *ctx, int *events, int request,
- void *arg)
-{
- return vo_x11_control(ctx->vo, events, request, arg);
+ assert(!surf->mapped);
+ gl->VDPAUMapSurfacesNV(1, &surf->registered);
+ surf->mapped = true;
+
+ ra_gl_ctx_resize(sw, surf->w, surf->h, surf->fbo);
+ return ra_gl_ctx_start_frame(sw, out_fbo);
}
-static void glx_start_frame(struct MPGLContext *ctx)
+static bool vdpau_submit_frame(struct ra_swapchain *sw,
+ const struct vo_frame *frame)
{
- struct priv *p = ctx->priv;
+ struct priv *p = sw->ctx->priv;
+ GL *gl = &p->gl;
struct vdp_functions *vdp = &p->vdp->vdp;
VdpStatus vdp_st;
- GL *gl = ctx->gl;
-
- struct surface *surface = &p->surfaces[p->current_surface];
-
- if (surface->surface != VDP_INVALID_HANDLE) {
- VdpTime prev_vsync_time;
- vdp_st = vdp->presentation_queue_block_until_surface_idle(p->vdp_queue,
- surface->surface,
- &prev_vsync_time);
- CHECK_VDP_WARNING(ctx, "waiting for surface failed");
- }
- if (surface->w != ctx->vo->dwidth || surface->h != ctx->vo->dheight)
- recreate_vdpau_surface(ctx, surface);
+ struct surface *surf = &p->surfaces[p->idx_surfaces];
+ assert(surf->surface != VDP_INVALID_HANDLE);
+ assert(surf->mapped);
+ gl->VDPAUUnmapSurfacesNV(1, &surf->registered);
+ surf->mapped = false;
+ vdp_st = vdp->presentation_queue_display(p->vdp_queue, surf->surface, 0, 0, 0);
+ CHECK_VDP_WARNING(sw->ctx, "trying to present vdp surface");
- ctx->main_fb = surface->fbo; // 0 if creating the surface failed
-
- if (surface->surface != VDP_INVALID_HANDLE) {
- gl->VDPAUMapSurfacesNV(1, &surface->registered);
- surface->mapped = true;
- }
+ p->idx_surfaces = (p->idx_surfaces + 1) % p->num_surfaces;
+ return ra_gl_ctx_submit_frame(sw, frame) && vdp_st == VDP_STATUS_OK;
}
-static void glx_swap_buffers(struct MPGLContext *ctx)
+static bool vdpau_reconfig(struct ra_ctx *ctx)
{
- struct priv *p = ctx->priv;
- struct vdp_functions *vdp = &p->vdp->vdp;
- VdpStatus vdp_st;
- GL *gl = ctx->gl;
-
- struct surface *surface = &p->surfaces[p->current_surface];
- if (surface->surface == VDP_INVALID_HANDLE)
- return; // surface alloc probably failed before
-
- if (surface->mapped)
- gl->VDPAUUnmapSurfacesNV(1, &surface->registered);
- surface->mapped = false;
-
- vdp_st = vdp->presentation_queue_display(p->vdp_queue, surface->surface,
- 0, 0, 0);
- CHECK_VDP_WARNING(ctx, "trying to present vdp surface");
+ vo_x11_config_vo_window(ctx->vo);
+ return true;
+}
- p->current_surface = (p->current_surface + 1) % p->num_surfaces;
+static int vdpau_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ return vo_x11_control(ctx->vo, events, request, arg);
}
-static void glx_wakeup(struct MPGLContext *ctx)
+static void vdpau_wakeup(struct ra_ctx *ctx)
{
vo_x11_wakeup(ctx->vo);
}
-static void glx_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+static void vdpau_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
{
vo_x11_wait_events(ctx->vo, until_time_us);
}
-const struct mpgl_driver mpgl_driver_vdpauglx = {
+static const struct ra_swapchain_fns vdpau_swapchain = {
+ .start_frame = vdpau_start_frame,
+ .submit_frame = vdpau_submit_frame,
+};
+
+const struct ra_ctx_fns ra_ctx_vdpauglx = {
+ .type = "opengl",
.name = "vdpauglx",
- .priv_size = sizeof(struct priv),
- .init = glx_init,
- .reconfig = glx_reconfig,
- .start_frame = glx_start_frame,
- .swap_buffers = glx_swap_buffers,
- .control = glx_control,
- .wakeup = glx_wakeup,
- .wait_events = glx_wait_events,
- .uninit = glx_uninit,
+ .reconfig = vdpau_reconfig,
+ .control = vdpau_control,
+ .wakeup = vdpau_wakeup,
+ .wait_events = vdpau_wait_events,
+ .init = vdpau_init,
+ .uninit = vdpau_uninit,
};
diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c
index 87e98cd..f686fcc 100644
--- a/video/out/opengl/context_wayland.c
+++ b/video/out/opengl/context_wayland.c
@@ -16,189 +16,166 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <wayland-egl.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
#include "video/out/wayland_common.h"
#include "context.h"
#include "egl_helpers.h"
+#include "utils.h"
+
+struct priv {
+ GL gl;
+ EGLDisplay egl_display;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+ EGLConfig egl_config;
+ struct wl_egl_window *egl_window;
+};
-static void egl_resize(struct vo_wayland_state *wl)
+static void resize(struct ra_ctx *ctx)
{
- int32_t x = wl->window.sh_x;
- int32_t y = wl->window.sh_y;
- int32_t width = wl->window.sh_width;
- int32_t height = wl->window.sh_height;
- int32_t scale = 1;
-
- if (!wl->egl_context.egl_window)
- return;
-
- if (wl->display.current_output)
- scale = wl->display.current_output->scale;
-
- // get the real size of the window
- // this improves moving the window while resizing it
- wl_egl_window_get_attached_size(wl->egl_context.egl_window,
- &wl->window.width,
- &wl->window.height);
+ struct priv *p = ctx->priv;
+ struct vo_wayland_state *wl = ctx->vo->wl;
- MP_VERBOSE(wl, "resizing %dx%d -> %dx%d\n", wl->window.width,
- wl->window.height,
- width,
- height);
+ MP_VERBOSE(wl, "Handling resize on the egl side\n");
- if (x != 0)
- x = wl->window.width - width;
+ const int32_t width = wl->scaling*mp_rect_w(wl->geometry);
+ const int32_t height = wl->scaling*mp_rect_h(wl->geometry);
- if (y != 0)
- y = wl->window.height - height;
+ wl_surface_set_buffer_scale(wl->surface, wl->scaling);
+ wl_egl_window_resize(p->egl_window, width, height, 0, 0);
- wl_surface_set_buffer_scale(wl->window.video_surface, scale);
- wl_egl_window_resize(wl->egl_context.egl_window, scale*width, scale*height, x, y);
-
- wl->window.width = width;
- wl->window.height = height;
+ wl->vo->dwidth = width;
+ wl->vo->dheight = height;
+}
- /* set size for mplayer */
- wl->vo->dwidth = scale*wl->window.width;
- wl->vo->dheight = scale*wl->window.height;
- wl->vo->want_redraw = true;
+static void wayland_egl_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
}
-static int egl_create_context(struct vo_wayland_state *wl, MPGLContext *ctx,
- int flags)
+static bool egl_create_context(struct ra_ctx *ctx)
{
- GL *gl = ctx->gl;
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct vo_wayland_state *wl = ctx->vo->wl;
- if (!(wl->egl_context.egl.dpy = eglGetDisplay(wl->display.display)))
- return -1;
+ if (!(p->egl_display = eglGetDisplay(wl->display)))
+ return false;
- if (eglInitialize(wl->egl_context.egl.dpy, NULL, NULL) != EGL_TRUE)
- return -1;
+ if (eglInitialize(p->egl_display, NULL, NULL) != EGL_TRUE)
+ return false;
- if (!mpegl_create_context(wl->egl_context.egl.dpy, wl->log, flags,
- &wl->egl_context.egl.ctx,
- &wl->egl_context.egl.conf))
- return -1;
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context,
+ &p->egl_config))
+ return false;
- eglMakeCurrent(wl->egl_context.egl.dpy, NULL, NULL, wl->egl_context.egl.ctx);
+ eglMakeCurrent(p->egl_display, NULL, NULL, p->egl_context);
- mpegl_load_functions(gl, wl->log);
+ mpegl_load_functions(&p->gl, wl->log);
- ctx->native_display_type = "wl";
- ctx->native_display = wl->display.display;
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = wayland_egl_swap_buffers,
+ .native_display_type = "wl",
+ .native_display = wl->display,
+ };
- return 0;
-}
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ return false;
-static void egl_create_window(struct vo_wayland_state *wl)
-{
- wl->egl_context.egl_window = wl_egl_window_create(wl->window.video_surface,
- wl->window.width,
- wl->window.height);
-
- wl->egl_context.egl_surface = eglCreateWindowSurface(wl->egl_context.egl.dpy,
- wl->egl_context.egl.conf,
- wl->egl_context.egl_window,
- NULL);
-
- eglMakeCurrent(wl->egl_context.egl.dpy,
- wl->egl_context.egl_surface,
- wl->egl_context.egl_surface,
- wl->egl_context.egl.ctx);
-
- wl_display_dispatch_pending(wl->display.display);
-
- /**
- * <http://lists.freedesktop.org/archives/wayland-devel/2013-November/012019.html>
- *
- * The main change is that if the swap interval is 0 then Mesa won't install a
- * frame callback so that eglSwapBuffers can be executed as often as necessary.
- * Instead it will do a sync request after the swap buffers. It will block for
- * sync complete event in get_back_bo instead of the frame callback. The
- * compositor is likely to send a release event while processing the new buffer
- * attach and this makes sure we will receive that before deciding whether to
- * allocate a new buffer.
- */
-
- eglSwapInterval(wl->egl_context.egl.dpy, 0);
+ return true;
}
-static int waylandgl_reconfig(struct MPGLContext *ctx)
+static void egl_create_window(struct ra_ctx *ctx)
{
- struct vo_wayland_state * wl = ctx->vo->wayland;
+ struct priv *p = ctx->priv;
+ struct vo_wayland_state *wl = ctx->vo->wl;
- if (!vo_wayland_config(ctx->vo))
- return -1;
+ p->egl_window = wl_egl_window_create(wl->surface, mp_rect_w(wl->geometry),
+ mp_rect_h(wl->geometry));
- if (!wl->egl_context.egl_window)
- egl_create_window(wl);
+ p->egl_surface = eglCreateWindowSurface(p->egl_display, p->egl_config,
+ p->egl_window, NULL);
- return 0;
+ eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, p->egl_context);
+
+ eglSwapInterval(p->egl_display, 0);
}
-static void waylandgl_uninit(MPGLContext *ctx)
+static bool wayland_egl_reconfig(struct ra_ctx *ctx)
{
- struct vo_wayland_state *wl = ctx->vo->wayland;
+ struct priv *p = ctx->priv;
- if (wl->egl_context.egl.ctx) {
- eglReleaseThread();
- if (wl->egl_context.egl_window)
- wl_egl_window_destroy(wl->egl_context.egl_window);
- eglDestroySurface(wl->egl_context.egl.dpy, wl->egl_context.egl_surface);
- eglMakeCurrent(wl->egl_context.egl.dpy, NULL, NULL, EGL_NO_CONTEXT);
- eglDestroyContext(wl->egl_context.egl.dpy, wl->egl_context.egl.ctx);
- }
- eglTerminate(wl->egl_context.egl.dpy);
- wl->egl_context.egl.ctx = NULL;
+ if (!vo_wayland_reconfig(ctx->vo))
+ return false;
- vo_wayland_uninit(ctx->vo);
+ if (!p->egl_window)
+ egl_create_window(ctx);
+
+ return true;
}
-static void waylandgl_swap_buffers(MPGLContext *ctx)
+static void wayland_egl_uninit(struct ra_ctx *ctx)
{
- struct vo_wayland_state *wl = ctx->vo->wayland;
+ struct priv *p = ctx->priv;
- vo_wayland_wait_events(ctx->vo, 0);
+ ra_gl_ctx_uninit(ctx);
- eglSwapBuffers(wl->egl_context.egl.dpy, wl->egl_context.egl_surface);
+ if (p->egl_context) {
+ eglReleaseThread();
+ if (p->egl_window)
+ wl_egl_window_destroy(p->egl_window);
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ eglMakeCurrent(p->egl_display, NULL, NULL, EGL_NO_CONTEXT);
+ eglDestroyContext(p->egl_display, p->egl_context);
+ p->egl_context = NULL;
+ }
+ eglTerminate(p->egl_display);
+
+ vo_wayland_uninit(ctx->vo);
}
-static int waylandgl_control(MPGLContext *ctx, int *events, int request,
+static int wayland_egl_control(struct ra_ctx *ctx, int *events, int request,
void *data)
{
- struct vo_wayland_state *wl = ctx->vo->wayland;
+ struct vo_wayland_state *wl = ctx->vo->wl;
int r = vo_wayland_control(ctx->vo, events, request, data);
- if (*events & VO_EVENT_RESIZE)
- egl_resize(wl);
+ if (*events & VO_EVENT_RESIZE) {
+ resize(ctx);
+ ra_gl_ctx_resize(ctx->swapchain, wl->vo->dwidth, wl->vo->dheight, 0);
+ }
return r;
}
-static void wayland_wakeup(struct MPGLContext *ctx)
+static void wayland_egl_wakeup(struct ra_ctx *ctx)
{
vo_wayland_wakeup(ctx->vo);
}
-static void wayland_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+static void wayland_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
{
vo_wayland_wait_events(ctx->vo, until_time_us);
}
-static int waylandgl_init(struct MPGLContext *ctx, int flags)
+static bool wayland_egl_init(struct ra_ctx *ctx)
{
if (!vo_wayland_init(ctx->vo))
- return -1;
+ return false;
- return egl_create_context(ctx->vo->wayland, ctx, flags);
+ return egl_create_context(ctx);
}
-const struct mpgl_driver mpgl_driver_wayland = {
+const struct ra_ctx_fns ra_ctx_wayland_egl = {
+ .type = "opengl",
.name = "wayland",
- .init = waylandgl_init,
- .reconfig = waylandgl_reconfig,
- .swap_buffers = waylandgl_swap_buffers,
- .control = waylandgl_control,
- .wakeup = wayland_wakeup,
- .wait_events = wayland_wait_events,
- .uninit = waylandgl_uninit,
+ .reconfig = wayland_egl_reconfig,
+ .control = wayland_egl_control,
+ .wakeup = wayland_egl_wakeup,
+ .wait_events = wayland_egl_wait_events,
+ .init = wayland_egl_init,
+ .uninit = wayland_egl_uninit,
};
diff --git a/video/out/opengl/context_w32.c b/video/out/opengl/context_win.c
index eb61239..5a0042b 100644
--- a/video/out/opengl/context_w32.c
+++ b/video/out/opengl/context_win.c
@@ -21,8 +21,8 @@
#include "options/m_config.h"
#include "video/out/w32_common.h"
-#include "video/out/win32/exclusive_hack.h"
#include "context.h"
+#include "utils.h"
#if !defined(WGL_CONTEXT_MAJOR_VERSION_ARB)
/* these are supposed to be defined in wingdi.h but mingw's is too old */
@@ -37,7 +37,9 @@
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#endif
-struct w32_context {
+struct priv {
+ GL gl;
+
int opt_swapinterval;
int current_swapinterval;
@@ -45,26 +47,25 @@ struct w32_context {
HGLRC context;
HDC hdc;
- int flags;
};
-static void w32_uninit(MPGLContext *ctx);
+static void wgl_uninit(struct ra_ctx *ctx);
-static __thread struct w32_context *current_w32_context;
+static __thread struct priv *current_wgl_context;
-static int GLAPIENTRY w32_swap_interval(int interval)
+static int GLAPIENTRY wgl_swap_interval(int interval)
{
- if (current_w32_context)
- current_w32_context->opt_swapinterval = interval;
+ if (current_wgl_context)
+ current_wgl_context->opt_swapinterval = interval;
return 0;
}
-static bool create_dc(struct MPGLContext *ctx, int flags)
+static bool create_dc(struct ra_ctx *ctx)
{
- struct w32_context *w32_ctx = ctx->priv;
+ struct priv *p = ctx->priv;
HWND win = vo_w32_hwnd(ctx->vo);
- if (w32_ctx->hdc)
+ if (p->hdc)
return true;
HDC hdc = GetDC(win);
@@ -90,11 +91,11 @@ static bool create_dc(struct MPGLContext *ctx, int flags)
SetPixelFormat(hdc, pf, &pfd);
- w32_ctx->hdc = hdc;
+ p->hdc = hdc;
return true;
}
-static void *w32gpa(const GLubyte *procName)
+static void *wglgpa(const GLubyte *procName)
{
HMODULE oglmod;
void *res = wglGetProcAddress(procName);
@@ -104,11 +105,11 @@ static void *w32gpa(const GLubyte *procName)
return GetProcAddress(oglmod, procName);
}
-static bool create_context_w32_old(struct MPGLContext *ctx)
+static bool create_context_wgl_old(struct ra_ctx *ctx)
{
- struct w32_context *w32_ctx = ctx->priv;
+ struct priv *p = ctx->priv;
- HDC windc = w32_ctx->hdc;
+ HDC windc = p->hdc;
bool res = false;
HGLRC context = wglCreateContext(windc);
@@ -123,17 +124,15 @@ static bool create_context_w32_old(struct MPGLContext *ctx)
return res;
}
- w32_ctx->context = context;
-
- mpgl_load_functions(ctx->gl, w32gpa, NULL, ctx->vo->log);
+ p->context = context;
return true;
}
-static bool create_context_w32_gl3(struct MPGLContext *ctx)
+static bool create_context_wgl_gl3(struct ra_ctx *ctx)
{
- struct w32_context *w32_ctx = ctx->priv;
+ struct priv *p = ctx->priv;
- HDC windc = w32_ctx->hdc;
+ HDC windc = p->hdc;
HGLRC context = 0;
// A legacy context is needed to get access to the new functions.
@@ -150,7 +149,7 @@ static bool create_context_w32_gl3(struct MPGLContext *ctx)
}
const char *(GLAPIENTRY *wglGetExtensionsStringARB)(HDC hdc)
- = w32gpa((const GLubyte*)"wglGetExtensionsStringARB");
+ = wglgpa((const GLubyte*)"wglGetExtensionsStringARB");
if (!wglGetExtensionsStringARB)
goto unsupported;
@@ -161,7 +160,7 @@ static bool create_context_w32_gl3(struct MPGLContext *ctx)
HGLRC (GLAPIENTRY *wglCreateContextAttribsARB)(HDC hDC, HGLRC hShareContext,
const int *attribList)
- = w32gpa((const GLubyte*)"wglCreateContextAttribsARB");
+ = wglgpa((const GLubyte*)"wglCreateContextAttribsARB");
if (!wglCreateContextAttribsARB)
goto unsupported;
@@ -197,11 +196,7 @@ static bool create_context_w32_gl3(struct MPGLContext *ctx)
return false;
}
- w32_ctx->context = context;
-
- /* update function pointers */
- mpgl_load_functions(ctx->gl, w32gpa, NULL, ctx->vo->log);
-
+ p->context = context;
return true;
unsupported:
@@ -214,79 +209,20 @@ out:
static void create_ctx(void *ptr)
{
- struct MPGLContext *ctx = ptr;
- struct w32_context *w32_ctx = ctx->priv;
+ struct ra_ctx *ctx = ptr;
+ struct priv *p = ctx->priv;
- if (!create_dc(ctx, w32_ctx->flags))
+ if (!create_dc(ctx))
return;
- create_context_w32_gl3(ctx);
- if (!w32_ctx->context)
- create_context_w32_old(ctx);
-
- wglMakeCurrent(w32_ctx->hdc, NULL);
-}
-
-static int w32_init(struct MPGLContext *ctx, int flags)
-{
- if (!vo_w32_init(ctx->vo))
- goto fail;
-
- struct w32_context *w32_ctx = ctx->priv;
+ create_context_wgl_gl3(ctx);
+ if (!p->context)
+ create_context_wgl_old(ctx);
- w32_ctx->flags = flags;
- vo_w32_run_on_thread(ctx->vo, create_ctx, ctx);
-
- if (!w32_ctx->context)
- goto fail;
-
- if (!ctx->gl->SwapInterval)
- MP_VERBOSE(ctx->vo, "WGL_EXT_swap_control missing.\n");
- w32_ctx->real_wglSwapInterval = ctx->gl->SwapInterval;
- ctx->gl->SwapInterval = w32_swap_interval;
- w32_ctx->current_swapinterval = -1;
-
- current_w32_context = w32_ctx;
- wglMakeCurrent(w32_ctx->hdc, w32_ctx->context);
- DwmEnableMMCSS(TRUE);
- return 0;
-
-fail:
- w32_uninit(ctx);
- return -1;
+ wglMakeCurrent(p->hdc, NULL);
}
-static int w32_reconfig(struct MPGLContext *ctx)
-{
- vo_w32_config(ctx->vo);
- return 0;
-}
-
-static void destroy_gl(void *ptr)
-{
- struct MPGLContext *ctx = ptr;
- struct w32_context *w32_ctx = ctx->priv;
- if (w32_ctx->context)
- wglDeleteContext(w32_ctx->context);
- w32_ctx->context = 0;
- if (w32_ctx->hdc)
- ReleaseDC(vo_w32_hwnd(ctx->vo), w32_ctx->hdc);
- w32_ctx->hdc = NULL;
- current_w32_context = NULL;
-}
-
-static void w32_uninit(MPGLContext *ctx)
-{
- struct w32_context *w32_ctx = ctx->priv;
- if (w32_ctx->context)
- wglMakeCurrent(w32_ctx->hdc, 0);
- vo_w32_run_on_thread(ctx->vo, destroy_gl, ctx);
-
- DwmEnableMMCSS(FALSE);
- vo_w32_uninit(ctx->vo);
-}
-
-static bool compositor_active(MPGLContext *ctx)
+static bool compositor_active(struct ra_ctx *ctx)
{
// For Windows 7.
BOOL enabled = 0;
@@ -300,21 +236,16 @@ static bool compositor_active(MPGLContext *ctx)
if (FAILED(DwmGetCompositionTimingInfo(0, &info)))
return false;
- // Test if a program is running in exclusive fullscreen mode. If so, it's
- // probably this one, so it's not getting redirected by the compositor.
- if (mp_w32_is_in_exclusive_mode())
- return false;
-
return true;
}
-static void w32_swap_buffers(MPGLContext *ctx)
+static void wgl_swap_buffers(struct ra_ctx *ctx)
{
- struct w32_context *w32_ctx = ctx->priv;
- SwapBuffers(w32_ctx->hdc);
+ struct priv *p = ctx->priv;
+ SwapBuffers(p->hdc);
// default if we don't DwmFLush
- int new_swapinterval = w32_ctx->opt_swapinterval;
+ int new_swapinterval = p->opt_swapinterval;
int dwm_flush_opt;
mp_read_option_raw(ctx->global, "opengl-dwmflush", &m_option_type_choice,
@@ -330,26 +261,103 @@ static void w32_swap_buffers(MPGLContext *ctx)
}
}
- if (new_swapinterval != w32_ctx->current_swapinterval &&
- w32_ctx->real_wglSwapInterval)
+ if (new_swapinterval != p->current_swapinterval &&
+ p->real_wglSwapInterval)
{
- w32_ctx->real_wglSwapInterval(new_swapinterval);
+ p->real_wglSwapInterval(new_swapinterval);
MP_VERBOSE(ctx->vo, "set SwapInterval(%d)\n", new_swapinterval);
}
- w32_ctx->current_swapinterval = new_swapinterval;
+ p->current_swapinterval = new_swapinterval;
+}
+
+static bool wgl_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ GL *gl = &p->gl;
+
+ if (!vo_w32_init(ctx->vo))
+ goto fail;
+
+ vo_w32_run_on_thread(ctx->vo, create_ctx, ctx);
+ if (!p->context)
+ goto fail;
+
+ current_wgl_context = p;
+ wglMakeCurrent(p->hdc, p->context);
+
+ mpgl_load_functions(gl, wglgpa, NULL, ctx->vo->log);
+
+ if (!gl->SwapInterval)
+ MP_VERBOSE(ctx->vo, "WGL_EXT_swap_control missing.\n");
+ p->real_wglSwapInterval = gl->SwapInterval;
+ gl->SwapInterval = wgl_swap_interval;
+ p->current_swapinterval = -1;
+
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = wgl_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto fail;
+
+ DwmEnableMMCSS(TRUE);
+ return true;
+
+fail:
+ wgl_uninit(ctx);
+ return false;
+}
+
+static void resize(struct ra_ctx *ctx)
+{
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
+}
+
+static bool wgl_reconfig(struct ra_ctx *ctx)
+{
+ vo_w32_config(ctx->vo);
+ resize(ctx);
+ return true;
+}
+
+static void destroy_gl(void *ptr)
+{
+ struct ra_ctx *ctx = ptr;
+ struct priv *p = ctx->priv;
+ if (p->context)
+ wglDeleteContext(p->context);
+ p->context = 0;
+ if (p->hdc)
+ ReleaseDC(vo_w32_hwnd(ctx->vo), p->hdc);
+ p->hdc = NULL;
+ current_wgl_context = NULL;
+}
+
+static void wgl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+ if (p->context)
+ wglMakeCurrent(p->hdc, 0);
+ vo_w32_run_on_thread(ctx->vo, destroy_gl, ctx);
+
+ DwmEnableMMCSS(FALSE);
+ vo_w32_uninit(ctx->vo);
}
-static int w32_control(MPGLContext *ctx, int *events, int request, void *arg)
+static int wgl_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
- return vo_w32_control(ctx->vo, events, request, arg);
+ int ret = vo_w32_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
}
-const struct mpgl_driver mpgl_driver_w32 = {
+const struct ra_ctx_fns ra_ctx_wgl = {
+ .type = "opengl",
.name = "win",
- .priv_size = sizeof(struct w32_context),
- .init = w32_init,
- .reconfig = w32_reconfig,
- .swap_buffers = w32_swap_buffers,
- .control = w32_control,
- .uninit = w32_uninit,
+ .init = wgl_init,
+ .reconfig = wgl_reconfig,
+ .control = wgl_control,
+ .uninit = wgl_uninit,
};
diff --git a/video/out/opengl/context_x11egl.c b/video/out/opengl/context_x11egl.c
index 2b68007..7ab4fe0 100644
--- a/video/out/opengl/context_x11egl.c
+++ b/video/out/opengl/context_x11egl.c
@@ -32,14 +32,17 @@
#include "egl_helpers.h"
struct priv {
+ GL gl;
EGLDisplay egl_display;
EGLContext egl_context;
EGLSurface egl_surface;
};
-static void mpegl_uninit(MPGLContext *ctx)
+static void mpegl_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
if (p->egl_context) {
eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
@@ -51,7 +54,7 @@ static void mpegl_uninit(MPGLContext *ctx)
static int pick_xrgba_config(void *user_data, EGLConfig *configs, int num_configs)
{
- struct MPGLContext *ctx = user_data;
+ struct ra_ctx *ctx = user_data;
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
@@ -72,40 +75,44 @@ static int pick_xrgba_config(void *user_data, EGLConfig *configs, int num_config
return 0;
}
-static int mpegl_init(struct MPGLContext *ctx, int flags)
+static void mpegl_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+}
+
+static bool mpegl_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
struct vo *vo = ctx->vo;
- int msgl = vo->probing ? MSGL_V : MSGL_FATAL;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_FATAL;
if (!vo_x11_init(vo))
goto uninit;
p->egl_display = eglGetDisplay(vo->x11->display);
if (!eglInitialize(p->egl_display, NULL, NULL)) {
- mp_msg(vo->log, msgl, "Could not initialize EGL.\n");
+ MP_MSG(ctx, msgl, "Could not initialize EGL.\n");
goto uninit;
}
- struct mpegl_opts opts = {
- .vo_flags = flags,
+ struct mpegl_cb cb = {
.user_data = ctx,
- .refine_config = (flags & VOFLAG_ALPHA) ? pick_xrgba_config : NULL,
+ .refine_config = ctx->opts.want_alpha ? pick_xrgba_config : NULL,
};
EGLConfig config;
- if (!mpegl_create_context_opts(p->egl_display, vo->log, &opts,
- &p->egl_context, &config))
+ if (!mpegl_create_context_cb(ctx, p->egl_display, cb, &p->egl_context, &config))
goto uninit;
int vID, n;
eglGetConfigAttrib(p->egl_display, config, EGL_NATIVE_VISUAL_ID, &vID);
- MP_VERBOSE(vo, "chose visual 0x%x\n", vID);
+ MP_VERBOSE(ctx, "chose visual 0x%x\n", vID);
XVisualInfo template = {.visualid = vID};
XVisualInfo *vi = XGetVisualInfo(vo->x11->display, VisualIDMask, &template, &n);
if (!vi) {
- MP_FATAL(vo, "Getting X visual failed!\n");
+ MP_FATAL(ctx, "Getting X visual failed!\n");
goto uninit;
}
@@ -120,64 +127,73 @@ static int mpegl_init(struct MPGLContext *ctx, int flags)
(EGLNativeWindowType)vo->x11->window, NULL);
if (p->egl_surface == EGL_NO_SURFACE) {
- MP_FATAL(ctx->vo, "Could not create EGL surface!\n");
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
goto uninit;
}
if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
p->egl_context))
{
- MP_FATAL(ctx->vo, "Could not make context current!\n");
+ MP_FATAL(ctx, "Could not make context current!\n");
goto uninit;
}
- mpegl_load_functions(ctx->gl, vo->log);
+ mpegl_load_functions(&p->gl, ctx->log);
- ctx->native_display_type = "x11";
- ctx->native_display = vo->x11->display;
- return 0;
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = mpegl_swap_buffers,
+ .native_display_type = "x11",
+ .native_display = vo->x11->display,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto uninit;
+
+ return true;
uninit:
mpegl_uninit(ctx);
- return -1;
+ return false;
}
-static int mpegl_reconfig(struct MPGLContext *ctx)
+static void resize(struct ra_ctx *ctx)
{
- vo_x11_config_vo_window(ctx->vo);
- return 0;
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
}
-static int mpegl_control(struct MPGLContext *ctx, int *events, int request,
- void *arg)
+static bool mpegl_reconfig(struct ra_ctx *ctx)
{
- return vo_x11_control(ctx->vo, events, request, arg);
+ vo_x11_config_vo_window(ctx->vo);
+ resize(ctx);
+ return true;
}
-static void mpegl_swap_buffers(MPGLContext *ctx)
+static int mpegl_control(struct ra_ctx *ctx, int *events, int request,
+ void *arg)
{
- struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl_display, p->egl_surface);
+ int ret = vo_x11_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
}
-static void mpegl_wakeup(struct MPGLContext *ctx)
+static void mpegl_wakeup(struct ra_ctx *ctx)
{
vo_x11_wakeup(ctx->vo);
}
-static void mpegl_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+static void mpegl_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
{
vo_x11_wait_events(ctx->vo, until_time_us);
}
-const struct mpgl_driver mpgl_driver_x11egl = {
+const struct ra_ctx_fns ra_ctx_x11_egl = {
+ .type = "opengl",
.name = "x11egl",
- .priv_size = sizeof(struct priv),
- .init = mpegl_init,
.reconfig = mpegl_reconfig,
- .swap_buffers = mpegl_swap_buffers,
.control = mpegl_control,
.wakeup = mpegl_wakeup,
.wait_events = mpegl_wait_events,
+ .init = mpegl_init,
.uninit = mpegl_uninit,
};
diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c
index ac152df..0033bf1 100644
--- a/video/out/opengl/egl_helpers.c
+++ b/video/out/opengl/egl_helpers.c
@@ -25,6 +25,7 @@
#include "egl_helpers.h"
#include "common.h"
+#include "utils.h"
#include "context.h"
#if HAVE_EGL_ANGLE
@@ -43,41 +44,49 @@
#define EGL_OPENGL_ES3_BIT 0x00000040
#endif
-// es_version = 0 (desktop), 2/3 (ES major version)
-static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
- int es_version, struct mpegl_opts *opts,
+// es_version: 0 (core), 2 or 3
+static bool create_context(struct ra_ctx *ctx, EGLDisplay display,
+ int es_version, struct mpegl_cb cb,
EGLContext *out_context, EGLConfig *out_config)
{
- int msgl = probing ? MSGL_V : MSGL_FATAL;
-
- EGLenum api = EGL_OPENGL_API;
- EGLint rend = EGL_OPENGL_BIT;
- const char *name = "Desktop OpenGL";
- if (es_version == 2) {
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_FATAL;
+
+ EGLenum api;
+ EGLint rend;
+ const char *name;
+
+ switch (es_version) {
+ case 0:
+ api = EGL_OPENGL_API;
+ rend = EGL_OPENGL_BIT;
+ name = "Desktop OpenGL";
+ break;
+ case 2:
api = EGL_OPENGL_ES_API;
rend = EGL_OPENGL_ES2_BIT;
- name = "GLES 2.0";
- }
- if (es_version == 3) {
+ name = "GLES 2.x";
+ break;
+ case 3:
api = EGL_OPENGL_ES_API;
rend = EGL_OPENGL_ES3_BIT;
name = "GLES 3.x";
+ break;
+ default: abort();
}
- mp_msg(log, MSGL_V, "Trying to create %s context.\n", name);
+ MP_VERBOSE(ctx, "Trying to create %s context.\n", name);
if (!eglBindAPI(api)) {
- mp_msg(log, MSGL_V, "Could not bind API!\n");
+ MP_VERBOSE(ctx, "Could not bind API!\n");
return false;
}
-
EGLint attributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
- EGL_ALPHA_SIZE, (opts->vo_flags & VOFLAG_ALPHA ) ? 1 : 0,
+ EGL_ALPHA_SIZE, ctx->opts.want_alpha ? 1 : 0,
EGL_RENDERABLE_TYPE, rend,
EGL_NONE
};
@@ -92,29 +101,34 @@ static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
if (!num_configs) {
talloc_free(configs);
- mp_msg(log, msgl, "Could not choose EGLConfig!\n");
+ MP_MSG(ctx, msgl, "Could not choose EGLConfig!\n");
return false;
}
int chosen = 0;
- if (opts->refine_config)
- chosen = opts->refine_config(opts->user_data, configs, num_configs);
+ if (cb.refine_config)
+ chosen = cb.refine_config(cb.user_data, configs, num_configs);
EGLConfig config = configs[chosen];
talloc_free(configs);
- EGLContext *ctx = NULL;
+ EGLContext *egl_ctx = NULL;
if (es_version) {
+ if (!ra_gl_ctx_test_version(ctx, MPGL_VER(es_version, 0), true))
+ return false;
+
EGLint attrs[] = {
EGL_CONTEXT_CLIENT_VERSION, es_version,
EGL_NONE
};
- ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
+ egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
} else {
for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
int ver = mpgl_preferred_gl_versions[n];
+ if (!ra_gl_ctx_test_version(ctx, ver, false))
+ continue;
EGLint attrs[] = {
EGL_CONTEXT_MAJOR_VERSION, MPGL_VER_GET_MAJOR(ver),
@@ -124,25 +138,25 @@ static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
EGL_NONE
};
- ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
- if (ctx)
+ egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
+ if (egl_ctx)
break;
}
- if (!ctx) {
+ if (!egl_ctx && ra_gl_ctx_test_version(ctx, 140, false)) {
// Fallback for EGL 1.4 without EGL_KHR_create_context.
EGLint attrs[] = { EGL_NONE };
- ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
+ egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
}
}
- if (!ctx) {
- mp_msg(log, msgl, "Could not create EGL context!\n");
+ if (!egl_ctx) {
+ MP_MSG(ctx, msgl, "Could not create EGL context!\n");
return false;
}
- *out_context = ctx;
+ *out_context = egl_ctx;
*out_config = config;
return true;
}
@@ -152,56 +166,36 @@ static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
// Create a context and return it and the config it was created with. If it
// returns false, the out_* pointers are set to NULL.
// vo_flags is a combination of VOFLAG_* values.
-bool mpegl_create_context(EGLDisplay display, struct mp_log *log, int vo_flags,
+bool mpegl_create_context(struct ra_ctx *ctx, EGLDisplay display,
EGLContext *out_context, EGLConfig *out_config)
{
- return mpegl_create_context_opts(display, log,
- &(struct mpegl_opts){.vo_flags = vo_flags}, out_context, out_config);
+ return mpegl_create_context_cb(ctx, display, (struct mpegl_cb){0},
+ out_context, out_config);
}
// Create a context and return it and the config it was created with. If it
// returns false, the out_* pointers are set to NULL.
-bool mpegl_create_context_opts(EGLDisplay display, struct mp_log *log,
- struct mpegl_opts *opts,
- EGLContext *out_context, EGLConfig *out_config)
+bool mpegl_create_context_cb(struct ra_ctx *ctx, EGLDisplay display,
+ struct mpegl_cb cb, EGLContext *out_context,
+ EGLConfig *out_config)
{
- assert(opts);
-
*out_context = NULL;
*out_config = NULL;
const char *version = eglQueryString(display, EGL_VERSION);
const char *vendor = eglQueryString(display, EGL_VENDOR);
const char *apis = eglQueryString(display, EGL_CLIENT_APIS);
- mp_verbose(log, "EGL_VERSION=%s\nEGL_VENDOR=%s\nEGL_CLIENT_APIS=%s\n",
+ MP_VERBOSE(ctx, "EGL_VERSION=%s\nEGL_VENDOR=%s\nEGL_CLIENT_APIS=%s\n",
STR_OR_ERR(version), STR_OR_ERR(vendor), STR_OR_ERR(apis));
- bool probing = opts->vo_flags & VOFLAG_PROBING;
- int msgl = probing ? MSGL_V : MSGL_FATAL;
- bool try_gles = !(opts->vo_flags & VOFLAG_NO_GLES);
-
- if (!(opts->vo_flags & VOFLAG_GLES)) {
- // Desktop OpenGL
- if (create_context(display, log, try_gles | probing, 0, opts,
- out_context, out_config))
- return true;
- }
-
- if (try_gles && !(opts->vo_flags & VOFLAG_GLES2)) {
- // ES 3.x
- if (create_context(display, log, true, 3, opts,
- out_context, out_config))
- return true;
- }
-
- if (try_gles) {
- // ES 2.0
- if (create_context(display, log, probing, 2, opts,
- out_context, out_config))
+ int es[] = {0, 3, 2}; // preference order
+ for (int i = 0; i < MP_ARRAY_SIZE(es); i++) {
+ if (create_context(ctx, display, es[i], cb, out_context, out_config))
return true;
}
- mp_msg(log, msgl, "Could not create a GL context.\n");
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+ MP_MSG(ctx, msgl, "Could not create a GL context.\n");
return false;
}
diff --git a/video/out/opengl/egl_helpers.h b/video/out/opengl/egl_helpers.h
index 05f9dcc..eaaf9d7 100644
--- a/video/out/opengl/egl_helpers.h
+++ b/video/out/opengl/egl_helpers.h
@@ -6,26 +6,23 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
+#include "video/out/gpu/context.h"
+
struct mp_log;
-bool mpegl_create_context(EGLDisplay display, struct mp_log *log, int vo_flags,
+bool mpegl_create_context(struct ra_ctx *ctx, EGLDisplay display,
EGLContext *out_context, EGLConfig *out_config);
-struct mpegl_opts {
- // combination of VOFLAG_* values.
- int vo_flags;
-
- // for callbacks
- void *user_data;
-
+struct mpegl_cb {
// if set, pick the desired config from the given list and return its index
// defaults to 0 (they are sorted by eglChooseConfig)
int (*refine_config)(void *user_data, EGLConfig *configs, int num_configs);
+ void *user_data;
};
-bool mpegl_create_context_opts(EGLDisplay display, struct mp_log *log,
- struct mpegl_opts *opts,
- EGLContext *out_context, EGLConfig *out_config);
+bool mpegl_create_context_cb(struct ra_ctx *ctx, EGLDisplay display,
+ struct mpegl_cb cb, EGLContext *out_context,
+ EGLConfig *out_config);
struct GL;
void mpegl_load_functions(struct GL *gl, struct mp_log *log);
diff --git a/video/out/opengl/formats.h b/video/out/opengl/formats.h
index 3da6ede..f727a3b 100644
--- a/video/out/opengl/formats.h
+++ b/video/out/opengl/formats.h
@@ -2,7 +2,6 @@
#define MPGL_FORMATS_H_
#include "common.h"
-#include "ra.h"
struct gl_format {
const char *name; // symbolic name for user interaction/debugging
diff --git a/video/out/opengl/gl_utils.c b/video/out/opengl/gl_utils.c
deleted file mode 100644
index bce2dab..0000000
--- a/video/out/opengl/gl_utils.c
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * This file is part of mpv.
- * Parts based on MPlayer code by Reimar Döffinger.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <assert.h>
-
-#include <libavutil/sha.h>
-#include <libavutil/intreadwrite.h>
-#include <libavutil/mem.h>
-
-#include "osdep/io.h"
-
-#include "common/common.h"
-#include "options/path.h"
-#include "stream/stream.h"
-#include "formats.h"
-#include "ra_gl.h"
-#include "gl_utils.h"
-
-// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL)
-static const char *gl_error_to_string(GLenum error)
-{
- switch (error) {
- case GL_INVALID_ENUM: return "INVALID_ENUM";
- case GL_INVALID_VALUE: return "INVALID_VALUE";
- case GL_INVALID_OPERATION: return "INVALID_OPERATION";
- case GL_INVALID_FRAMEBUFFER_OPERATION: return "INVALID_FRAMEBUFFER_OPERATION";
- case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
- default: return "unknown";
- }
-}
-
-void gl_check_error(GL *gl, struct mp_log *log, const char *info)
-{
- for (;;) {
- GLenum error = gl->GetError();
- if (error == GL_NO_ERROR)
- break;
- mp_msg(log, MSGL_ERR, "%s: OpenGL error %s.\n", info,
- gl_error_to_string(error));
- }
-}
-
-static int get_alignment(int stride)
-{
- if (stride % 8 == 0)
- return 8;
- if (stride % 4 == 0)
- return 4;
- if (stride % 2 == 0)
- return 2;
- return 1;
-}
-
-// upload a texture, handling things like stride and slices
-// target: texture target, usually GL_TEXTURE_2D
-// format, type: texture parameters
-// dataptr, stride: image data
-// x, y, width, height: part of the image to upload
-void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
- const void *dataptr, int stride,
- int x, int y, int w, int h)
-{
- int bpp = gl_bytes_per_pixel(format, type);
- const uint8_t *data = dataptr;
- int y_max = y + h;
- if (w <= 0 || h <= 0 || !bpp)
- return;
- if (stride < 0) {
- data += (h - 1) * stride;
- stride = -stride;
- }
- gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(stride));
- int slice = h;
- if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH) {
- // this is not always correct, but should work for MPlayer
- gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / bpp);
- } else {
- if (stride != bpp * w)
- slice = 1; // very inefficient, but at least it works
- }
- for (; y + slice <= y_max; y += slice) {
- gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, data);
- data += stride * slice;
- }
- if (y < y_max)
- gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, data);
- if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH)
- gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
- gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
-}
-
-mp_image_t *gl_read_fbo_contents(GL *gl, int fbo, int w, int h)
-{
- if (gl->es)
- return NULL; // ES can't read from front buffer
- mp_image_t *image = mp_image_alloc(IMGFMT_RGB24, w, h);
- if (!image)
- return NULL;
- gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
- GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
- gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
- gl->ReadBuffer(obj);
- //flip image while reading (and also avoid stride-related trouble)
- for (int y = 0; y < h; y++) {
- gl->ReadPixels(0, h - y - 1, w, 1, GL_RGB, GL_UNSIGNED_BYTE,
- image->planes[0] + y * image->stride[0]);
- }
- gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
- gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
- return image;
-}
-
-static void gl_vao_enable_attribs(struct gl_vao *vao)
-{
- GL *gl = vao->gl;
-
- for (int n = 0; n < vao->num_entries; n++) {
- const struct ra_renderpass_input *e = &vao->entries[n];
- GLenum type = 0;
- bool normalized = false;
- switch (e->type) {
- case RA_VARTYPE_INT:
- type = GL_INT;
- break;
- case RA_VARTYPE_FLOAT:
- type = GL_FLOAT;
- break;
- case RA_VARTYPE_BYTE_UNORM:
- type = GL_UNSIGNED_BYTE;
- normalized = true;
- break;
- default:
- abort();
- }
- assert(e->dim_m == 1);
-
- gl->EnableVertexAttribArray(n);
- gl->VertexAttribPointer(n, e->dim_v, type, normalized,
- vao->stride, (void *)(intptr_t)e->offset);
- }
-}
-
-void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
- const struct ra_renderpass_input *entries,
- int num_entries)
-{
- assert(!vao->vao);
- assert(!vao->buffer);
-
- *vao = (struct gl_vao){
- .gl = gl,
- .stride = stride,
- .entries = entries,
- .num_entries = num_entries,
- };
-
- gl->GenBuffers(1, &vao->buffer);
-
- if (gl->BindVertexArray) {
- gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
-
- gl->GenVertexArrays(1, &vao->vao);
- gl->BindVertexArray(vao->vao);
- gl_vao_enable_attribs(vao);
- gl->BindVertexArray(0);
-
- gl->BindBuffer(GL_ARRAY_BUFFER, 0);
- }
-}
-
-void gl_vao_uninit(struct gl_vao *vao)
-{
- GL *gl = vao->gl;
- if (!gl)
- return;
-
- if (gl->DeleteVertexArrays)
- gl->DeleteVertexArrays(1, &vao->vao);
- gl->DeleteBuffers(1, &vao->buffer);
-
- *vao = (struct gl_vao){0};
-}
-
-static void gl_vao_bind(struct gl_vao *vao)
-{
- GL *gl = vao->gl;
-
- if (gl->BindVertexArray) {
- gl->BindVertexArray(vao->vao);
- } else {
- gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
- gl_vao_enable_attribs(vao);
- gl->BindBuffer(GL_ARRAY_BUFFER, 0);
- }
-}
-
-static void gl_vao_unbind(struct gl_vao *vao)
-{
- GL *gl = vao->gl;
-
- if (gl->BindVertexArray) {
- gl->BindVertexArray(0);
- } else {
- for (int n = 0; n < vao->num_entries; n++)
- gl->DisableVertexAttribArray(n);
- }
-}
-
-// Draw the vertex data (as described by the gl_vao_entry entries) in ptr
-// to the screen. num is the number of vertexes. prim is usually GL_TRIANGLES.
-// If ptr is NULL, then skip the upload, and use the data uploaded with the
-// previous call.
-void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num)
-{
- GL *gl = vao->gl;
-
- if (ptr) {
- gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
- gl->BufferData(GL_ARRAY_BUFFER, num * vao->stride, ptr, GL_STREAM_DRAW);
- gl->BindBuffer(GL_ARRAY_BUFFER, 0);
- }
-
- gl_vao_bind(vao);
-
- gl->DrawArrays(prim, 0, num);
-
- gl_vao_unbind(vao);
-}
-
-static void GLAPIENTRY gl_debug_cb(GLenum source, GLenum type, GLuint id,
- GLenum severity, GLsizei length,
- const GLchar *message, const void *userParam)
-{
- // keep in mind that the debug callback can be asynchronous
- struct mp_log *log = (void *)userParam;
- int level = MSGL_ERR;
- switch (severity) {
- case GL_DEBUG_SEVERITY_NOTIFICATION:level = MSGL_V; break;
- case GL_DEBUG_SEVERITY_LOW: level = MSGL_INFO; break;
- case GL_DEBUG_SEVERITY_MEDIUM: level = MSGL_WARN; break;
- case GL_DEBUG_SEVERITY_HIGH: level = MSGL_ERR; break;
- }
- mp_msg(log, level, "GL: %s\n", message);
-}
-
-void gl_set_debug_logger(GL *gl, struct mp_log *log)
-{
- if (gl->DebugMessageCallback)
- gl->DebugMessageCallback(log ? gl_debug_cb : NULL, log);
-}
-
-int gl_get_fb_depth(GL *gl, int fbo)
-{
- if ((gl->es < 300 && !gl->version) || !(gl->mpgl_caps & MPGL_CAP_FB))
- return -1;
-
- gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
-
- GLenum obj = gl->version ? GL_BACK_LEFT : GL_BACK;
- if (fbo)
- obj = GL_COLOR_ATTACHMENT0;
-
- GLint depth_g = -1;
-
- gl->GetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, obj,
- GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, &depth_g);
-
- gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
-
- return depth_g > 0 ? depth_g : -1;
-}
diff --git a/video/out/opengl/gl_utils.h b/video/out/opengl/gl_utils.h
deleted file mode 100644
index 306ee23..0000000
--- a/video/out/opengl/gl_utils.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * This file is part of mpv.
- * Parts based on MPlayer code by Reimar Döffinger.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MP_GL_UTILS_
-#define MP_GL_UTILS_
-
-#include <math.h>
-
-#include "common.h"
-#include "ra.h"
-
-struct mp_log;
-
-void gl_check_error(GL *gl, struct mp_log *log, const char *info);
-
-void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
- const void *dataptr, int stride,
- int x, int y, int w, int h);
-
-mp_image_t *gl_read_fbo_contents(GL *gl, int fbo, int w, int h);
-
-struct gl_vao {
- GL *gl;
- GLuint vao; // the VAO object, or 0 if unsupported by driver
- GLuint buffer; // GL_ARRAY_BUFFER used for the data
- int stride; // size of each element (interleaved elements are assumed)
- const struct ra_renderpass_input *entries;
- int num_entries;
-};
-
-void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
- const struct ra_renderpass_input *entries,
- int num_entries);
-void gl_vao_uninit(struct gl_vao *vao);
-void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num);
-
-void gl_set_debug_logger(GL *gl, struct mp_log *log);
-
-int gl_get_fb_depth(GL *gl, int fbo);
-
-#endif
diff --git a/video/out/opengl/hwdec_cuda.c b/video/out/opengl/hwdec_cuda.c
index d40bafe..1a7df20 100644
--- a/video/out/opengl/hwdec_cuda.c
+++ b/video/out/opengl/hwdec_cuda.c
@@ -32,11 +32,10 @@
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_cuda.h>
+#include "video/out/gpu/hwdec.h"
#include "formats.h"
-#include "hwdec.h"
#include "options/m_config.h"
#include "ra_gl.h"
-#include "video.h"
struct priv_owner {
struct mp_hwdec_ctx hwctx;
@@ -161,11 +160,9 @@ static int cuda_init(struct ra_hwdec *hw)
goto error;
p->hwctx = (struct mp_hwdec_ctx) {
- .type = HWDEC_CUDA,
- .ctx = p->decode_ctx,
+ .driver_name = hw->driver->name,
.av_device_ref = hw_device_ctx,
};
- p->hwctx.driver_name = hw->driver->name;
hwdec_devices_add(hw->devs, &p->hwctx);
return 0;
@@ -180,8 +177,7 @@ static void cuda_uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
av_buffer_unref(&p->hwctx.av_device_ref);
if (p->decode_ctx && p->decode_ctx != p->display_ctx)
@@ -327,8 +323,7 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
}
const struct ra_hwdec_driver ra_hwdec_cuda = {
- .name = "cuda",
- .api = HWDEC_CUDA,
+ .name = "cuda-nvdec",
.imgfmts = {IMGFMT_CUDA, 0},
.priv_size = sizeof(struct priv_owner),
.init = cuda_init,
diff --git a/video/out/opengl/hwdec_d3d11egl.c b/video/out/opengl/hwdec_d3d11egl.c
index 3988f83..e741633 100644
--- a/video/out/opengl/hwdec_d3d11egl.c
+++ b/video/out/opengl/hwdec_d3d11egl.c
@@ -27,10 +27,10 @@
#include "common/common.h"
#include "osdep/timer.h"
#include "osdep/windows_utils.h"
-#include "hwdec.h"
+#include "video/out/gpu/hwdec.h"
#include "ra_gl.h"
#include "video/hwdec.h"
-#include "video/decode/d3d.h"
+#include "video/d3d.h"
#ifndef EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
#define EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE 0x33AB
@@ -75,8 +75,7 @@ static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
if (p->d3d11_device)
ID3D11Device_Release(p->d3d11_device);
@@ -180,10 +179,7 @@ static int init(struct ra_hwdec *hw)
ID3D10Multithread_Release(multithread);
p->hwctx = (struct mp_hwdec_ctx){
- .type = HWDEC_D3D11VA,
.driver_name = hw->driver->name,
- .ctx = p->d3d11_device,
- .download_image = d3d11_download_image,
.av_device_ref = d3d11_wrap_device_ref(p->d3d11_device),
};
hwdec_devices_add(hw->devs, &p->hwctx);
@@ -336,7 +332,6 @@ static void mapper_unmap(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_d3d11egl = {
.name = "d3d11-egl",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_D3D11VA,
.imgfmts = {IMGFMT_D3D11NV12, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_d3d11eglrgb.c b/video/out/opengl/hwdec_d3d11eglrgb.c
index fa3976f..c8f6580 100644
--- a/video/out/opengl/hwdec_d3d11eglrgb.c
+++ b/video/out/opengl/hwdec_d3d11eglrgb.c
@@ -27,10 +27,10 @@
#include "common/common.h"
#include "osdep/timer.h"
#include "osdep/windows_utils.h"
-#include "hwdec.h"
+#include "video/out/gpu/hwdec.h"
#include "ra_gl.h"
#include "video/hwdec.h"
-#include "video/decode/d3d.h"
+#include "video/d3d.h"
#ifndef EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
#define EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE 0x3AAB
@@ -54,8 +54,7 @@ static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
if (p->d3d11_device)
ID3D11Device_Release(p->d3d11_device);
@@ -137,9 +136,7 @@ static int init(struct ra_hwdec *hw)
}
p->hwctx = (struct mp_hwdec_ctx){
- .type = HWDEC_D3D11VA,
.driver_name = hw->driver->name,
- .ctx = p->d3d11_device,
.av_device_ref = d3d11_wrap_device_ref(p->d3d11_device),
};
hwdec_devices_add(hw->devs, &p->hwctx);
@@ -261,7 +258,6 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_d3d11eglrgb = {
.name = "d3d11-egl-rgb",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_D3D11VA,
.imgfmts = {IMGFMT_D3D11RGB, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_drmprime_drm.c b/video/out/opengl/hwdec_drmprime_drm.c
new file mode 100644
index 0000000..faa099a
--- /dev/null
+++ b/video/out/opengl/hwdec_drmprime_drm.c
@@ -0,0 +1,268 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+
+#include <libavutil/hwcontext_drm.h>
+
+#include "common.h"
+#include "video/hwdec.h"
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "libmpv/opengl_cb.h"
+#include "video/out/drm_common.h"
+#include "video/out/drm_prime.h"
+#include "video/out/gpu/hwdec.h"
+#include "video/mp_image.h"
+
+#include "ra_gl.h"
+
+extern const struct m_sub_options drm_conf;
+
+struct drm_frame {
+ struct drm_prime_framebuffer fb;
+ struct mp_image *image; // associated mpv image
+};
+
+struct priv {
+ struct mp_log *log;
+
+ struct mp_image_params params;
+
+ struct drm_atomic_context *ctx;
+ struct drm_frame current_frame, old_frame;
+
+ struct mp_rect src, dst;
+
+ int display_w, display_h;
+};
+
+static void set_current_frame(struct ra_hwdec *hw, struct drm_frame *frame)
+{
+ struct priv *p = hw->priv;
+
+ // frame will be on screen after next vsync
+ // current_frame is currently the displayed frame and will be replaced
+ // by frame after next vsync.
+ // We used old frame as triple buffering to make sure that the drm framebuffer
+ // is not being displayed when we release it.
+
+ if (p->ctx) {
+ drm_prime_destroy_framebuffer(p->log, p->ctx->fd, &p->old_frame.fb);
+ }
+
+ mp_image_setrefp(&p->old_frame.image, p->current_frame.image);
+ p->old_frame.fb = p->current_frame.fb;
+
+ if (frame) {
+ p->current_frame.fb = frame->fb;
+ mp_image_setrefp(&p->current_frame.image, frame->image);
+ } else {
+ memset(&p->current_frame.fb, 0, sizeof(p->current_frame.fb));
+ mp_image_setrefp(&p->current_frame.image, NULL);
+ }
+}
+
+static void scale_dst_rect(struct ra_hwdec *hw, int source_w, int source_h ,struct mp_rect *src, struct mp_rect *dst)
+{
+ struct priv *p = hw->priv;
+ double hratio, vratio, ratio;
+
+ // drm can allow to have a layer that has a different size from framebuffer
+ // we scale here the destination size to video mode
+ hratio = vratio = ratio = 1.0;
+
+ hratio = (double)p->display_w / (double)source_w;
+ vratio = (double)p->display_h / (double)source_h;
+ ratio = hratio <= vratio ? hratio : vratio;
+
+ dst->x0 = src->x0 * ratio;
+ dst->x1 = src->x1 * ratio;
+ dst->y0 = src->y0 * ratio;
+ dst->y1 = src->y1 * ratio;
+
+ int offset_x = (p->display_w - ratio * source_w) / 2;
+ int offset_y = (p->display_h - ratio * source_h) / 2;
+
+ dst->x0 += offset_x;
+ dst->x1 += offset_x;
+ dst->y0 += offset_y;
+ dst->y1 += offset_y;
+}
+
+static int overlay_frame(struct ra_hwdec *hw, struct mp_image *hw_image,
+ struct mp_rect *src, struct mp_rect *dst, bool newframe)
+{
+ struct priv *p = hw->priv;
+ GL *gl = ra_gl_get(hw->ra);
+ AVDRMFrameDescriptor *desc = NULL;
+ drmModeAtomicReq *request = NULL;
+ struct drm_frame next_frame = {0};
+ int ret;
+
+ if (hw_image) {
+
+ // grab opengl-cb windowing info to eventually upscale the overlay
+ // as egl windows could be upscaled to primary plane.
+ struct mpv_opengl_cb_window_pos *glparams =
+ gl ? (struct mpv_opengl_cb_window_pos *)
+ mpgl_get_native_display(gl, "opengl-cb-window-pos") : NULL;
+ if (glparams) {
+ scale_dst_rect(hw, glparams->width, glparams->height, dst, &p->dst);
+ } else {
+ p->dst = *dst;
+ }
+ p->src = *src;
+
+ // grab drm interop info
+ struct mpv_opengl_cb_drm_params *drmparams =
+ gl ? (struct mpv_opengl_cb_drm_params *)
+ mpgl_get_native_display(gl, "opengl-cb-drm-params") : NULL;
+ if (drmparams)
+ request = (drmModeAtomicReq *)drmparams->atomic_request;
+
+ next_frame.image = hw_image;
+ desc = (AVDRMFrameDescriptor *)hw_image->planes[0];
+
+ if (desc) {
+ int srcw = p->src.x1 - p->src.x0;
+ int srch = p->src.y1 - p->src.y0;
+ int dstw = MP_ALIGN_UP(p->dst.x1 - p->dst.x0, 2);
+ int dsth = MP_ALIGN_UP(p->dst.y1 - p->dst.y0, 2);
+
+ if (drm_prime_create_framebuffer(p->log, p->ctx->fd, desc, srcw, srch, &next_frame.fb)) {
+ ret = -1;
+ goto fail;
+ }
+
+ if (request) {
+ drm_object_set_property(request, p->ctx->overlay_plane, "FB_ID", next_frame.fb.fb_id);
+ drm_object_set_property(request, p->ctx->overlay_plane, "CRTC_ID", p->ctx->crtc->id);
+ drm_object_set_property(request, p->ctx->overlay_plane, "SRC_X", p->src.x0 << 16);
+ drm_object_set_property(request, p->ctx->overlay_plane, "SRC_Y", p->src.y0 << 16);
+ drm_object_set_property(request, p->ctx->overlay_plane, "SRC_W", srcw << 16);
+ drm_object_set_property(request, p->ctx->overlay_plane, "SRC_H", srch << 16);
+ drm_object_set_property(request, p->ctx->overlay_plane, "CRTC_X", MP_ALIGN_DOWN(p->dst.x0, 2));
+ drm_object_set_property(request, p->ctx->overlay_plane, "CRTC_Y", MP_ALIGN_DOWN(p->dst.y0, 2));
+ drm_object_set_property(request, p->ctx->overlay_plane, "CRTC_W", dstw);
+ drm_object_set_property(request, p->ctx->overlay_plane, "CRTC_H", dsth);
+ drm_object_set_property(request, p->ctx->overlay_plane, "ZPOS", 0);
+ } else {
+ ret = drmModeSetPlane(p->ctx->fd, p->ctx->overlay_plane->id, p->ctx->crtc->id, next_frame.fb.fb_id, 0,
+ MP_ALIGN_DOWN(p->dst.x0, 2), MP_ALIGN_DOWN(p->dst.y0, 2), dstw, dsth,
+ p->src.x0 << 16, p->src.y0 << 16 , srcw << 16, srch << 16);
+ if (ret < 0) {
+ MP_ERR(hw, "Failed to set the plane %d (buffer %d).\n", p->ctx->overlay_plane->id,
+ next_frame.fb.fb_id);
+ goto fail;
+ }
+ }
+ }
+ }
+
+ set_current_frame(hw, &next_frame);
+ return 0;
+
+ fail:
+ drm_prime_destroy_framebuffer(p->log, p->ctx->fd, &next_frame.fb);
+ return ret;
+}
+
+static void uninit(struct ra_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+
+ set_current_frame(hw, NULL);
+
+ if (p->ctx) {
+ drm_atomic_destroy_context(p->ctx);
+ p->ctx = NULL;
+ }
+}
+
+static int init(struct ra_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+ int drm_overlay;
+
+ if (!ra_is_gl(hw->ra))
+ return -1;
+
+ p->log = hw->log;
+
+ void *tmp = talloc_new(NULL);
+ struct drm_opts *opts = mp_get_config_group(tmp, hw->global, &drm_conf);
+ drm_overlay = opts->drm_overlay_id;
+ talloc_free(tmp);
+
+ GL *gl = ra_gl_get(hw->ra);
+ struct mpv_opengl_cb_drm_params *params =
+ gl ? (struct mpv_opengl_cb_drm_params *)
+ mpgl_get_native_display(gl, "opengl-cb-drm-params") : NULL;
+ if (!params) {
+ MP_VERBOSE(hw, "Could not get drm interop info.\n");
+ goto err;
+ }
+
+ if (params->fd) {
+ p->ctx = drm_atomic_create_context(p->log, params->fd, params->crtc_id,
+ drm_overlay);
+ if (!p->ctx) {
+ mp_err(p->log, "Failed to retrieve DRM atomic context.\n");
+ goto err;
+ }
+ } else {
+ mp_err(p->log, "Failed to retrieve DRM fd from native display.\n");
+ goto err;
+ }
+
+ drmModeCrtcPtr crtc;
+ crtc = drmModeGetCrtc(p->ctx->fd, p->ctx->crtc->id);
+ if (crtc) {
+ p->display_w = crtc->mode.hdisplay;
+ p->display_h = crtc->mode.vdisplay;
+ drmModeFreeCrtc(crtc);
+ }
+
+
+ uint64_t has_prime;
+ if (drmGetCap(p->ctx->fd, DRM_CAP_PRIME, &has_prime) < 0) {
+ MP_ERR(hw, "Card does not support prime handles.\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ uninit(hw);
+ return -1;
+}
+
+const struct ra_hwdec_driver ra_hwdec_drmprime_drm = {
+ .name = "drmprime-drm",
+ .priv_size = sizeof(struct priv),
+ .imgfmts = {IMGFMT_DRMPRIME, 0},
+ .init = init,
+ .overlay_frame = overlay_frame,
+ .uninit = uninit,
+};
diff --git a/video/out/opengl/hwdec_dxva2egl.c b/video/out/opengl/hwdec_dxva2egl.c
index 01fb482..0f8a4ad 100644
--- a/video/out/opengl/hwdec_dxva2egl.c
+++ b/video/out/opengl/hwdec_dxva2egl.c
@@ -27,10 +27,10 @@
#include "common/common.h"
#include "osdep/timer.h"
#include "osdep/windows_utils.h"
-#include "hwdec.h"
+#include "video/out/gpu/hwdec.h"
#include "ra_gl.h"
#include "video/hwdec.h"
-#include "video/decode/d3d.h"
+#include "video/d3d.h"
struct priv_owner {
struct mp_hwdec_ctx hwctx;
@@ -58,8 +58,8 @@ static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
+ av_buffer_unref(&p->hwctx.av_device_ref);
if (p->device9ex)
IDirect3DDevice9Ex_Release(p->device9ex);
@@ -180,9 +180,7 @@ static int init(struct ra_hwdec *hw)
ra_hwdec_mapper_free(&mapper);
p->hwctx = (struct mp_hwdec_ctx){
- .type = HWDEC_DXVA2,
.driver_name = hw->driver->name,
- .ctx = (IDirect3DDevice9 *)p->device9ex,
.av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)p->device9ex),
};
hwdec_devices_add(hw->devs, &p->hwctx);
@@ -368,7 +366,6 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_dxva2egl = {
.name = "dxva2-egl",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_DXVA2,
.imgfmts = {IMGFMT_DXVA2, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_dxva2gldx.c b/video/out/opengl/hwdec_dxva2gldx.c
index fd9c80b..984fd7f 100644
--- a/video/out/opengl/hwdec_dxva2gldx.c
+++ b/video/out/opengl/hwdec_dxva2gldx.c
@@ -20,10 +20,10 @@
#include "common/common.h"
#include "osdep/windows_utils.h"
-#include "hwdec.h"
+#include "video/out/gpu/hwdec.h"
#include "ra_gl.h"
#include "video/hwdec.h"
-#include "video/decode/d3d.h"
+#include "video/d3d.h"
// for WGL_ACCESS_READ_ONLY_NV
#include <GL/wglext.h>
@@ -48,8 +48,8 @@ static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
+ av_buffer_unref(&p->hwctx.av_device_ref);
if (p->device)
IDirect3DDevice9Ex_Release(p->device);
@@ -78,9 +78,7 @@ static int init(struct ra_hwdec *hw)
IDirect3DDevice9Ex_AddRef(p->device);
p->hwctx = (struct mp_hwdec_ctx){
- .type = HWDEC_DXVA2,
.driver_name = hw->driver->name,
- .ctx = (IDirect3DDevice9 *)p->device,
.av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)p->device),
};
hwdec_devices_add(hw->devs, &p->hwctx);
@@ -229,7 +227,6 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_dxva2gldx = {
.name = "dxva2-dxinterop",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_DXVA2,
.imgfmts = {IMGFMT_DXVA2, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_ios.m b/video/out/opengl/hwdec_ios.m
index 8e020de..b8d4876 100644
--- a/video/out/opengl/hwdec_ios.m
+++ b/video/out/opengl/hwdec_ios.m
@@ -27,10 +27,9 @@
#include "config.h"
+#include "video/out/gpu/hwdec.h"
#include "video/mp_image_pool.h"
-#include "video/vt.h"
#include "ra_gl.h"
-#include "hwdec.h"
struct priv_owner {
struct mp_hwdec_ctx hwctx;
@@ -70,15 +69,11 @@ static int init(struct ra_hwdec *hw)
return -1;
p->hwctx = (struct mp_hwdec_ctx){
- .type = HWDEC_VIDEOTOOLBOX,
- .download_image = mp_vt_download_image,
- .ctx = &p->hwctx,
+ .driver_name = hw->driver->name,
};
-#if HAVE_VIDEOTOOLBOX_HWACCEL_NEW
av_hwdevice_ctx_create(&p->hwctx.av_device_ref, AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
NULL, NULL, 0);
-#endif
hwdec_devices_add(hw->devs, &p->hwctx);
@@ -89,8 +84,7 @@ static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
av_buffer_unref(&p->hwctx.av_device_ref);
}
@@ -132,7 +126,6 @@ static const struct ra_format *find_la_variant(struct ra *ra,
static int mapper_init(struct ra_hwdec_mapper *mapper)
{
struct priv *p = mapper->priv;
- GL *gl = ra_gl_get(mapper->ra);
mapper->dst_params = mapper->src_params;
mapper->dst_params.imgfmt = mapper->src_params.hw_subfmt;
@@ -243,8 +236,11 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
.src_linear = true,
};
- mapper->tex[i] = ra_create_wrapped_tex(mapper->ra, &params,
- p->gl_planes[i]);
+ mapper->tex[i] = ra_create_wrapped_tex(
+ mapper->ra,
+ &params,
+ CVOpenGLESTextureGetName(p->gl_planes[i])
+ );
if (!mapper->tex[i])
return -1;
}
@@ -264,7 +260,6 @@ static void mapper_uninit(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_videotoolbox = {
.name = "videotoolbox",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_VIDEOTOOLBOX,
.imgfmts = {IMGFMT_VIDEOTOOLBOX, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_osx.c b/video/out/opengl/hwdec_osx.c
index 348a5e1..ca7a004 100644
--- a/video/out/opengl/hwdec_osx.c
+++ b/video/out/opengl/hwdec_osx.c
@@ -29,9 +29,8 @@
#include "config.h"
#include "video/mp_image_pool.h"
-#include "video/vt.h"
+#include "video/out/gpu/hwdec.h"
#include "ra_gl.h"
-#include "hwdec.h"
struct priv_owner {
struct mp_hwdec_ctx hwctx;
@@ -71,15 +70,11 @@ static int init(struct ra_hwdec *hw)
return -1;
p->hwctx = (struct mp_hwdec_ctx){
- .type = HWDEC_VIDEOTOOLBOX,
- .download_image = mp_vt_download_image,
- .ctx = &p->hwctx,
+ .driver_name = hw->driver->name,
};
-#if HAVE_VIDEOTOOLBOX_HWACCEL_NEW
av_hwdevice_ctx_create(&p->hwctx.av_device_ref, AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
NULL, NULL, 0);
-#endif
hwdec_devices_add(hw->devs, &p->hwctx);
@@ -90,8 +85,7 @@ static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
- if (p->hwctx.ctx)
- hwdec_devices_remove(hw->devs, &p->hwctx);
+ hwdec_devices_remove(hw->devs, &p->hwctx);
av_buffer_unref(&p->hwctx.av_device_ref);
}
@@ -214,7 +208,6 @@ static void mapper_uninit(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_videotoolbox = {
.name = "videotoolbox",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_VIDEOTOOLBOX,
.imgfmts = {IMGFMT_VIDEOTOOLBOX, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_rpi.c b/video/out/opengl/hwdec_rpi.c
index 6f39c3e..6c080f1 100644
--- a/video/out/opengl/hwdec_rpi.c
+++ b/video/out/opengl/hwdec_rpi.c
@@ -33,8 +33,8 @@
#include "common/common.h"
#include "common/msg.h"
#include "video/mp_image.h"
+#include "video/out/gpu/hwdec.h"
-#include "hwdec.h"
#include "common.h"
#include "ra_gl.h"
@@ -378,7 +378,6 @@ static int create(struct ra_hwdec *hw)
const struct ra_hwdec_driver ra_hwdec_rpi_overlay = {
.name = "rpi-overlay",
- .api = HWDEC_RPI,
.priv_size = sizeof(struct priv),
.imgfmts = {IMGFMT_MMAL, IMGFMT_420P, 0},
.init = create,
diff --git a/video/out/opengl/hwdec_vaegl.c b/video/out/opengl/hwdec_vaegl.c
index a0e3222..b4587c5 100644
--- a/video/out/opengl/hwdec_vaegl.c
+++ b/video/out/opengl/hwdec_vaegl.c
@@ -18,6 +18,7 @@
#include <stddef.h>
#include <string.h>
#include <assert.h>
+#include <unistd.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -30,9 +31,9 @@
#include "config.h"
-#include "hwdec.h"
-#include "video/vaapi.h"
+#include "video/out/gpu/hwdec.h"
#include "video/mp_image_pool.h"
+#include "video/vaapi.h"
#include "common.h"
#include "ra_gl.h"
@@ -127,6 +128,11 @@ struct priv {
EGLImageKHR images[4];
VAImage current_image;
bool buffer_acquired;
+#if VA_CHECK_VERSION(1, 1, 0)
+ bool esh_not_implemented;
+ VADRMPRIMESurfaceDescriptor desc;
+ bool surface_acquired;
+#endif
EGLImageKHR (EGLAPIENTRY *CreateImageKHR)(EGLDisplay, EGLContext,
EGLenum, EGLClientBuffer,
@@ -209,6 +215,14 @@ static void mapper_unmap(struct ra_hwdec_mapper *mapper)
p->images[n] = 0;
}
+#if VA_CHECK_VERSION(1, 1, 0)
+ if (p->surface_acquired) {
+ for (int n = 0; n < p->desc.num_objects; n++)
+ close(p->desc.objects[n].fd);
+ p->surface_acquired = false;
+ }
+#endif
+
if (p->buffer_acquired) {
status = vaReleaseBufferHandle(display, p->current_image.buf);
CHECK_VA_STATUS(mapper, "vaReleaseBufferHandle()");
@@ -330,6 +344,72 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
VAImage *va_image = &p->current_image;
VADisplay *display = p_owner->display;
+#if VA_CHECK_VERSION(1, 1, 0)
+ if (p->esh_not_implemented)
+ goto esh_failed;
+
+ status = vaExportSurfaceHandle(display, va_surface_id(mapper->src),
+ VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
+ VA_EXPORT_SURFACE_READ_ONLY |
+ VA_EXPORT_SURFACE_SEPARATE_LAYERS,
+ &p->desc);
+ if (!CHECK_VA_STATUS(mapper, "vaAcquireSurfaceHandle()")) {
+ if (status == VA_STATUS_ERROR_UNIMPLEMENTED)
+ p->esh_not_implemented = true;
+ goto esh_failed;
+ }
+ p->surface_acquired = true;
+
+ for (int n = 0; n < p->num_planes; n++) {
+ int attribs[20] = {EGL_NONE};
+ int num_attribs = 0;
+
+ ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, p->desc.layers[n].drm_format);
+ ADD_ATTRIB(EGL_WIDTH, p->tex[n]->params.w);
+ ADD_ATTRIB(EGL_HEIGHT, p->tex[n]->params.h);
+
+#define ADD_PLANE_ATTRIBS(plane) do { \
+ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _FD_EXT, \
+ p->desc.objects[p->desc.layers[n].object_index[plane]].fd); \
+ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _OFFSET_EXT, \
+ p->desc.layers[n].offset[plane]); \
+ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _PITCH_EXT, \
+ p->desc.layers[n].pitch[plane]); \
+ } while (0)
+
+ ADD_PLANE_ATTRIBS(0);
+ if (p->desc.layers[n].num_planes > 1)
+ ADD_PLANE_ATTRIBS(1);
+ if (p->desc.layers[n].num_planes > 2)
+ ADD_PLANE_ATTRIBS(2);
+ if (p->desc.layers[n].num_planes > 3)
+ ADD_PLANE_ATTRIBS(3);
+
+ p->images[n] = p->CreateImageKHR(eglGetCurrentDisplay(),
+ EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+ if (!p->images[n])
+ goto esh_failed;
+
+ gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]);
+ p->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, p->images[n]);
+
+ mapper->tex[n] = p->tex[n];
+ }
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+
+ if (p->desc.fourcc == VA_FOURCC_YV12)
+ MPSWAP(struct ra_tex*, mapper->tex[1], mapper->tex[2]);
+
+ return 0;
+
+esh_failed:
+ if (p->surface_acquired) {
+ for (int n = 0; n < p->desc.num_objects; n++)
+ close(p->desc.objects[n].fd);
+ p->surface_acquired = false;
+ }
+#endif
+
status = vaDeriveImage(display, va_surface_id(mapper->src), va_image);
if (!CHECK_VA_STATUS(mapper, "vaDeriveImage()"))
goto err;
@@ -417,7 +497,7 @@ static void determine_working_formats(struct ra_hwdec *hw)
AVHWFramesConstraints *fc =
av_hwdevice_get_hwframe_constraints(p->ctx->av_device_ref, NULL);
if (!fc) {
- MP_WARN(hw, "failed to retrieve libavutil frame constaints\n");
+ MP_WARN(hw, "failed to retrieve libavutil frame constraints\n");
goto done;
}
for (int n = 0; fc->valid_sw_formats[n] != AV_PIX_FMT_NONE; n++) {
@@ -464,7 +544,6 @@ done:
const struct ra_hwdec_driver ra_hwdec_vaegl = {
.name = "vaapi-egl",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_VAAPI,
.imgfmts = {IMGFMT_VAAPI, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/hwdec_vaglx.c b/video/out/opengl/hwdec_vaglx.c
deleted file mode 100644
index 8db15c4..0000000
--- a/video/out/opengl/hwdec_vaglx.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * Parts based on the MPlayer VA-API patch (see vo_vaapi.c).
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stddef.h>
-#include <string.h>
-#include <assert.h>
-
-#include <GL/glx.h>
-#include <va/va_x11.h>
-
-#include "video/out/x11_common.h"
-#include "ra_gl.h"
-#include "hwdec.h"
-#include "video/vaapi.h"
-
-struct priv_owner {
- struct mp_vaapi_ctx *ctx;
- VADisplay *display;
- Display *xdisplay;
- GLXFBConfig fbc;
-};
-
-struct priv {
- GLuint gl_texture;
- Pixmap pixmap;
- GLXPixmap glxpixmap;
- void (*glXBindTexImage)(Display *dpy, GLXDrawable draw, int buffer, int *a);
- void (*glXReleaseTexImage)(Display *dpy, GLXDrawable draw, int buffer);
-};
-
-static void uninit(struct ra_hwdec *hw)
-{
- struct priv_owner *p = hw->priv;
- if (p->ctx)
- hwdec_devices_remove(hw->devs, &p->ctx->hwctx);
- va_destroy(p->ctx);
-}
-
-static int init(struct ra_hwdec *hw)
-{
- Display *x11disp = glXGetCurrentDisplay();
- if (!x11disp || !ra_is_gl(hw->ra))
- return -1;
- int x11scr = DefaultScreen(x11disp);
- struct priv_owner *p = hw->priv;
- p->xdisplay = x11disp;
- const char *glxext = glXQueryExtensionsString(x11disp, x11scr);
- if (!glxext || !strstr(glxext, "GLX_EXT_texture_from_pixmap"))
- return -1;
- p->display = vaGetDisplay(x11disp);
- if (!p->display)
- return -1;
- p->ctx = va_initialize(p->display, hw->log, true);
- if (!p->ctx) {
- vaTerminate(p->display);
- return -1;
- }
-
- int attribs[] = {
- GLX_BIND_TO_TEXTURE_RGBA_EXT, True,
- GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
- GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
- GLX_Y_INVERTED_EXT, True,
- GLX_DOUBLEBUFFER, False,
- GLX_RED_SIZE, 8,
- GLX_GREEN_SIZE, 8,
- GLX_BLUE_SIZE, 8,
- GLX_ALPHA_SIZE, 0,
- None
- };
-
- int fbcount;
- GLXFBConfig *fbc = glXChooseFBConfig(x11disp, x11scr, attribs, &fbcount);
- if (fbcount)
- p->fbc = fbc[0];
- if (fbc)
- XFree(fbc);
- if (!fbcount) {
- MP_VERBOSE(hw, "No texture-from-pixmap support.\n");
- return -1;
- }
-
- p->ctx->hwctx.driver_name = hw->driver->name;
- hwdec_devices_add(hw->devs, &p->ctx->hwctx);
- return 0;
-}
-
-static int mapper_init(struct ra_hwdec_mapper *mapper)
-{
- struct priv_owner *p_owner = mapper->owner->priv;
- struct priv *p = mapper->priv;
- GL *gl = ra_gl_get(mapper->ra);
- Display *xdisplay = p_owner->xdisplay;
-
- p->glXBindTexImage =
- (void*)glXGetProcAddressARB((void*)"glXBindTexImageEXT");
- p->glXReleaseTexImage =
- (void*)glXGetProcAddressARB((void*)"glXReleaseTexImageEXT");
- if (!p->glXBindTexImage || !p->glXReleaseTexImage)
- return -1;
-
- gl->GenTextures(1, &p->gl_texture);
- gl->BindTexture(GL_TEXTURE_2D, p->gl_texture);
- gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- gl->BindTexture(GL_TEXTURE_2D, 0);
-
- p->pixmap = XCreatePixmap(xdisplay,
- RootWindow(xdisplay, DefaultScreen(xdisplay)),
- mapper->src_params.w, mapper->src_params.h, 24);
- if (!p->pixmap) {
- MP_FATAL(mapper, "could not create pixmap\n");
- return -1;
- }
-
- int attribs[] = {
- GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
- GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGB_EXT,
- GLX_MIPMAP_TEXTURE_EXT, False,
- None,
- };
- p->glxpixmap = glXCreatePixmap(xdisplay, p_owner->fbc, p->pixmap, attribs);
-
- gl->BindTexture(GL_TEXTURE_2D, p->gl_texture);
- p->glXBindTexImage(xdisplay, p->glxpixmap, GLX_FRONT_EXT, NULL);
- gl->BindTexture(GL_TEXTURE_2D, 0);
-
- struct ra_tex_params params = {
- .dimensions = 2,
- .w = mapper->src_params.w,
- .h = mapper->src_params.h,
- .d = 1,
- .format = ra_find_unorm_format(mapper->ra, 1, 4), // unsure
- .render_src = true,
- .src_linear = true,
- };
- if (!params.format)
- return -1;
-
- mapper->tex[0] = ra_create_wrapped_tex(mapper->ra, &params, p->gl_texture);
- if (!mapper->tex[0])
- return -1;
-
- mapper->dst_params = mapper->src_params;
- mapper->dst_params.imgfmt = IMGFMT_RGB0;
- mapper->dst_params.hw_subfmt = 0;
-
- return 0;
-}
-
-static void mapper_uninit(struct ra_hwdec_mapper *mapper)
-{
- struct priv_owner *p_owner = mapper->owner->priv;
- struct priv *p = mapper->priv;
- GL *gl = ra_gl_get(mapper->ra);
- Display *xdisplay = p_owner->xdisplay;
-
- if (p->glxpixmap) {
- p->glXReleaseTexImage(xdisplay, p->glxpixmap, GLX_FRONT_EXT);
- glXDestroyPixmap(xdisplay, p->glxpixmap);
- }
- p->glxpixmap = 0;
-
- if (p->pixmap)
- XFreePixmap(xdisplay, p->pixmap);
- p->pixmap = 0;
-
- ra_tex_free(mapper->ra, &mapper->tex[0]);
- gl->DeleteTextures(1, &p->gl_texture);
- p->gl_texture = 0;
-}
-
-static int mapper_map(struct ra_hwdec_mapper *mapper)
-{
- struct priv_owner *p_owner = mapper->owner->priv;
- struct priv *p = mapper->priv;
- VAStatus status;
-
- struct mp_image *hw_image = mapper->src;
-
- if (!p->pixmap)
- return -1;
-
- status = vaPutSurface(p_owner->display, va_surface_id(hw_image), p->pixmap,
- 0, 0, hw_image->w, hw_image->h,
- 0, 0, hw_image->w, hw_image->h,
- NULL, 0,
- va_get_colorspace_flag(hw_image->params.color.space));
- CHECK_VA_STATUS(mapper, "vaPutSurface()");
-
- return 0;
-}
-
-const struct ra_hwdec_driver ra_hwdec_vaglx = {
- .name = "vaapi-glx",
- .priv_size = sizeof(struct priv_owner),
- .api = HWDEC_VAAPI,
- .imgfmts = {IMGFMT_VAAPI, 0},
- .testing_only = true,
- .init = init,
- .uninit = uninit,
- .mapper = &(const struct ra_hwdec_mapper_driver){
- .priv_size = sizeof(struct priv),
- .init = mapper_init,
- .uninit = mapper_uninit,
- .map = mapper_map,
- },
-};
diff --git a/video/out/opengl/hwdec_vdpau.c b/video/out/opengl/hwdec_vdpau.c
index d733650..603a70e 100644
--- a/video/out/opengl/hwdec_vdpau.c
+++ b/video/out/opengl/hwdec_vdpau.c
@@ -20,7 +20,7 @@
#include <GL/glx.h>
-#include "hwdec.h"
+#include "video/out/gpu/hwdec.h"
#include "ra_gl.h"
#include "video/vdpau.h"
#include "video/vdpau_mixer.h"
@@ -304,7 +304,6 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
const struct ra_hwdec_driver ra_hwdec_vdpau = {
.name = "vdpau-glx",
.priv_size = sizeof(struct priv_owner),
- .api = HWDEC_VDPAU,
.imgfmts = {IMGFMT_VDPAU, 0},
.init = init,
.uninit = uninit,
diff --git a/video/out/opengl/ra_gl.c b/video/out/opengl/ra_gl.c
index ab5c132..5b03368 100644
--- a/video/out/opengl/ra_gl.c
+++ b/video/out/opengl/ra_gl.c
@@ -96,14 +96,12 @@ static int ra_init_gl(struct ra *ra, GL *gl)
static const int caps_map[][2] = {
{RA_CAP_DIRECT_UPLOAD, 0},
- {RA_CAP_SHARED_BINDING, 0},
{RA_CAP_GLOBAL_UNIFORM, 0},
+ {RA_CAP_FRAGCOORD, 0},
{RA_CAP_TEX_1D, MPGL_CAP_1D_TEX},
{RA_CAP_TEX_3D, MPGL_CAP_3D_TEX},
{RA_CAP_COMPUTE, MPGL_CAP_COMPUTE_SHADER},
{RA_CAP_NESTED_ARRAY, MPGL_CAP_NESTED_ARRAY},
- {RA_CAP_BUF_RO, MPGL_CAP_UBO},
- {RA_CAP_BUF_RW, MPGL_CAP_SSBO},
};
for (int i = 0; i < MP_ARRAY_SIZE(caps_map); i++) {
@@ -111,6 +109,17 @@ static int ra_init_gl(struct ra *ra, GL *gl)
ra->caps |= caps_map[i][0];
}
+ if (gl->BindBufferBase) {
+ if (gl->mpgl_caps & MPGL_CAP_UBO)
+ ra->caps |= RA_CAP_BUF_RO;
+ if (gl->mpgl_caps & MPGL_CAP_SSBO)
+ ra->caps |= RA_CAP_BUF_RW;
+ }
+
+ // textureGather is only supported in GLSL 400+
+ if (ra->glsl_version >= 400)
+ ra->caps |= RA_CAP_GATHER;
+
if (gl->BlitFramebuffer)
ra->caps |= RA_CAP_BLIT;
@@ -175,6 +184,8 @@ static int ra_init_gl(struct ra *ra, GL *gl)
desc->chroma_w = desc->chroma_h = 1;
}
+ fmt->glsl_format = ra_fmt_glsl_format(fmt);
+
MP_TARRAY_APPEND(ra, ra->formats, ra->num_formats, fmt);
}
@@ -648,6 +659,11 @@ static void gl_blit(struct ra *ra, struct ra_tex *dst, struct ra_tex *src,
gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
+static int gl_desc_namespace(enum ra_vartype type)
+{
+ return type;
+}
+
static void gl_renderpass_destroy(struct ra *ra, struct ra_renderpass *pass)
{
GL *gl = ra_gl_get(ra);
@@ -773,7 +789,7 @@ static GLuint load_program(struct ra *ra, const struct ra_renderpass_params *p,
GLint status = 0;
gl->GetProgramiv(prog, GL_LINK_STATUS, &status);
if (status) {
- MP_VERBOSE(ra, "Loading binary program succeeded.\n");
+ MP_DBG(ra, "Loading binary program succeeded.\n");
} else {
gl->DeleteProgram(prog);
prog = 0;
@@ -811,7 +827,7 @@ static struct ra_renderpass *gl_renderpass_create(struct ra *ra,
GL *gl = ra_gl_get(ra);
struct ra_renderpass *pass = talloc_zero(NULL, struct ra_renderpass);
- pass->params = *ra_render_pass_params_copy(pass, params);
+ pass->params = *ra_renderpass_params_copy(pass, params);
pass->params.cached_program = (bstr){0};
struct ra_renderpass_gl *pass_gl = pass->priv =
talloc_zero(NULL, struct ra_renderpass_gl);
@@ -1097,12 +1113,6 @@ static uint64_t gl_timer_stop(struct ra *ra, ra_timer *ratimer)
return timer->result;
}
-static void gl_flush(struct ra *ra)
-{
- GL *gl = ra_gl_get(ra);
- gl->Flush();
-}
-
static void gl_debug_marker(struct ra *ra, const char *msg)
{
struct ra_gl *p = ra->priv;
@@ -1123,6 +1133,7 @@ static struct ra_fns ra_fns_gl = {
.clear = gl_clear,
.blit = gl_blit,
.uniform_layout = std140_layout,
+ .desc_namespace = gl_desc_namespace,
.renderpass_create = gl_renderpass_create,
.renderpass_destroy = gl_renderpass_destroy,
.renderpass_run = gl_renderpass_run,
@@ -1130,6 +1141,5 @@ static struct ra_fns ra_fns_gl = {
.timer_destroy = gl_timer_destroy,
.timer_start = gl_timer_start,
.timer_stop = gl_timer_stop,
- .flush = gl_flush,
.debug_marker = gl_debug_marker,
};
diff --git a/video/out/opengl/ra_gl.h b/video/out/opengl/ra_gl.h
index e5e09a0..9844977 100644
--- a/video/out/opengl/ra_gl.h
+++ b/video/out/opengl/ra_gl.h
@@ -1,8 +1,7 @@
#pragma once
#include "common.h"
-#include "ra.h"
-#include "gl_utils.h"
+#include "utils.h"
struct ra *ra_create_gl(GL *gl, struct mp_log *log);
struct ra_tex *ra_create_wrapped_tex(struct ra *ra,
diff --git a/video/out/opengl/utils.c b/video/out/opengl/utils.c
index b8fc24a..34f4736 100644
--- a/video/out/opengl/utils.c
+++ b/video/out/opengl/utils.c
@@ -1,371 +1,284 @@
-#include "common/msg.h"
-#include "video/out/vo.h"
+/*
+ * This file is part of mpv.
+ * Parts based on MPlayer code by Reimar Döffinger.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include <libavutil/sha.h>
+#include <libavutil/intreadwrite.h>
+#include <libavutil/mem.h>
+
+#include "osdep/io.h"
+
+#include "common/common.h"
+#include "options/path.h"
+#include "stream/stream.h"
+#include "formats.h"
#include "utils.h"
-// Standard parallel 2D projection, except y1 < y0 means that the coordinate
-// system is flipped, not the projection.
-void gl_transform_ortho(struct gl_transform *t, float x0, float x1,
- float y0, float y1)
+// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL)
+static const char *gl_error_to_string(GLenum error)
{
- if (y1 < y0) {
- float tmp = y0;
- y0 = tmp - y1;
- y1 = tmp;
+ switch (error) {
+ case GL_INVALID_ENUM: return "INVALID_ENUM";
+ case GL_INVALID_VALUE: return "INVALID_VALUE";
+ case GL_INVALID_OPERATION: return "INVALID_OPERATION";
+ case GL_INVALID_FRAMEBUFFER_OPERATION: return "INVALID_FRAMEBUFFER_OPERATION";
+ case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
+ default: return "unknown";
}
-
- t->m[0][0] = 2.0f / (x1 - x0);
- t->m[0][1] = 0.0f;
- t->m[1][0] = 0.0f;
- t->m[1][1] = 2.0f / (y1 - y0);
- t->t[0] = -(x1 + x0) / (x1 - x0);
- t->t[1] = -(y1 + y0) / (y1 - y0);
-}
-
-// Apply the effects of one transformation to another, transforming it in the
-// process. In other words: post-composes t onto x
-void gl_transform_trans(struct gl_transform t, struct gl_transform *x)
-{
- struct gl_transform xt = *x;
- x->m[0][0] = t.m[0][0] * xt.m[0][0] + t.m[0][1] * xt.m[1][0];
- x->m[1][0] = t.m[1][0] * xt.m[0][0] + t.m[1][1] * xt.m[1][0];
- x->m[0][1] = t.m[0][0] * xt.m[0][1] + t.m[0][1] * xt.m[1][1];
- x->m[1][1] = t.m[1][0] * xt.m[0][1] + t.m[1][1] * xt.m[1][1];
- gl_transform_vec(t, &x->t[0], &x->t[1]);
-}
-
-void gl_transform_ortho_fbodst(struct gl_transform *t, struct fbodst fbo)
-{
- int y_dir = fbo.flip ? -1 : 1;
- gl_transform_ortho(t, 0, fbo.tex->params.w, 0, fbo.tex->params.h * y_dir);
}
-void ra_buf_pool_uninit(struct ra *ra, struct ra_buf_pool *pool)
+void gl_check_error(GL *gl, struct mp_log *log, const char *info)
{
- for (int i = 0; i < pool->num_buffers; i++)
- ra_buf_free(ra, &pool->buffers[i]);
-
- talloc_free(pool->buffers);
- *pool = (struct ra_buf_pool){0};
+ for (;;) {
+ GLenum error = gl->GetError();
+ if (error == GL_NO_ERROR)
+ break;
+ mp_msg(log, MSGL_ERR, "%s: OpenGL error %s.\n", info,
+ gl_error_to_string(error));
+ }
}
-static bool ra_buf_params_compatible(const struct ra_buf_params *new,
- const struct ra_buf_params *old)
+static int get_alignment(int stride)
{
- return new->type == old->type &&
- new->size <= old->size &&
- new->host_mapped == old->host_mapped &&
- new->host_mutable == old->host_mutable;
+ if (stride % 8 == 0)
+ return 8;
+ if (stride % 4 == 0)
+ return 4;
+ if (stride % 2 == 0)
+ return 2;
+ return 1;
}
-static bool ra_buf_pool_grow(struct ra *ra, struct ra_buf_pool *pool)
+// upload a texture, handling things like stride and slices
+// target: texture target, usually GL_TEXTURE_2D
+// format, type: texture parameters
+// dataptr, stride: image data
+// x, y, width, height: part of the image to upload
+void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
+ const void *dataptr, int stride,
+ int x, int y, int w, int h)
{
- struct ra_buf *buf = ra_buf_create(ra, &pool->current_params);
- if (!buf)
- return false;
-
- MP_TARRAY_INSERT_AT(NULL, pool->buffers, pool->num_buffers, pool->index, buf);
- MP_VERBOSE(ra, "Resized buffer pool to size %d\n", pool->num_buffers);
- return true;
+ int bpp = gl_bytes_per_pixel(format, type);
+ const uint8_t *data = dataptr;
+ int y_max = y + h;
+ if (w <= 0 || h <= 0 || !bpp)
+ return;
+ assert(stride > 0);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(stride));
+ int slice = h;
+ if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH) {
+ // this is not always correct, but should work for MPlayer
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / bpp);
+ } else {
+ if (stride != bpp * w)
+ slice = 1; // very inefficient, but at least it works
+ }
+ for (; y + slice <= y_max; y += slice) {
+ gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, data);
+ data += stride * slice;
+ }
+ if (y < y_max)
+ gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, data);
+ if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH)
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
-struct ra_buf *ra_buf_pool_get(struct ra *ra, struct ra_buf_pool *pool,
- const struct ra_buf_params *params)
+mp_image_t *gl_read_fbo_contents(GL *gl, int fbo, int w, int h)
{
- assert(!params->initial_data);
-
- if (!ra_buf_params_compatible(params, &pool->current_params)) {
- ra_buf_pool_uninit(ra, pool);
- pool->current_params = *params;
- }
-
- // Make sure we have at least one buffer available
- if (!pool->buffers && !ra_buf_pool_grow(ra, pool))
- return NULL;
-
- // Make sure the next buffer is available for use
- if (!ra->fns->buf_poll(ra, pool->buffers[pool->index]) &&
- !ra_buf_pool_grow(ra, pool))
- {
+ if (gl->es)
+ return NULL; // ES can't read from front buffer
+ mp_image_t *image = mp_image_alloc(IMGFMT_RGB24, w, h);
+ if (!image)
return NULL;
+ gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
+ gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
+ gl->ReadBuffer(obj);
+ //flip image while reading (and also avoid stride-related trouble)
+ for (int y = 0; y < h; y++) {
+ gl->ReadPixels(0, h - y - 1, w, 1, GL_RGB, GL_UNSIGNED_BYTE,
+ image->planes[0] + y * image->stride[0]);
}
-
- struct ra_buf *buf = pool->buffers[pool->index++];
- pool->index %= pool->num_buffers;
-
- return buf;
+ gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+ return image;
}
-bool ra_tex_upload_pbo(struct ra *ra, struct ra_buf_pool *pbo,
- const struct ra_tex_upload_params *params)
+static void gl_vao_enable_attribs(struct gl_vao *vao)
{
- if (params->buf)
- return ra->fns->tex_upload(ra, params);
-
- struct ra_tex *tex = params->tex;
- size_t row_size = tex->params.dimensions == 2 ? params->stride :
- tex->params.w * tex->params.format->pixel_size;
-
- struct ra_buf_params bufparams = {
- .type = RA_BUF_TYPE_TEX_UPLOAD,
- .size = row_size * tex->params.h * tex->params.d,
- .host_mutable = true,
- };
-
- struct ra_buf *buf = ra_buf_pool_get(ra, pbo, &bufparams);
- if (!buf)
- return false;
-
- ra->fns->buf_update(ra, buf, 0, params->src, bufparams.size);
-
- struct ra_tex_upload_params newparams = *params;
- newparams.buf = buf;
- newparams.src = NULL;
-
- return ra->fns->tex_upload(ra, &newparams);
-}
+ GL *gl = vao->gl;
+
+ for (int n = 0; n < vao->num_entries; n++) {
+ const struct ra_renderpass_input *e = &vao->entries[n];
+ GLenum type = 0;
+ bool normalized = false;
+ switch (e->type) {
+ case RA_VARTYPE_INT:
+ type = GL_INT;
+ break;
+ case RA_VARTYPE_FLOAT:
+ type = GL_FLOAT;
+ break;
+ case RA_VARTYPE_BYTE_UNORM:
+ type = GL_UNSIGNED_BYTE;
+ normalized = true;
+ break;
+ default:
+ abort();
+ }
+ assert(e->dim_m == 1);
-struct ra_layout std140_layout(struct ra_renderpass_input *inp)
-{
- size_t el_size = ra_vartype_size(inp->type);
-
- // std140 packing rules:
- // 1. The alignment of generic values is their size in bytes
- // 2. The alignment of vectors is the vector length * the base count, with
- // the exception of vec3 which is always aligned like vec4
- // 3. The alignment of arrays is that of the element size rounded up to
- // the nearest multiple of vec4
- // 4. Matrices are treated like arrays of vectors
- // 5. Arrays/matrices are laid out with a stride equal to the alignment
- size_t size = el_size * inp->dim_v;
- if (inp->dim_v == 3)
- size += el_size;
- if (inp->dim_m > 1)
- size = MP_ALIGN_UP(size, sizeof(float[4]));
-
- return (struct ra_layout) {
- .align = size,
- .stride = size,
- .size = size * inp->dim_m,
- };
+ gl->EnableVertexAttribArray(n);
+ gl->VertexAttribPointer(n, e->dim_v, type, normalized,
+ vao->stride, (void *)(intptr_t)e->offset);
+ }
}
-struct ra_layout std430_layout(struct ra_renderpass_input *inp)
+void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
+ const struct ra_renderpass_input *entries,
+ int num_entries)
{
- size_t el_size = ra_vartype_size(inp->type);
-
- // std430 packing rules: like std140, except arrays/matrices are always
- // "tightly" packed, even arrays/matrices of vec3s
- size_t align = el_size * inp->dim_v;
- if (inp->dim_v == 3 && inp->dim_m == 1)
- align += el_size;
-
- return (struct ra_layout) {
- .align = align,
- .stride = align,
- .size = align * inp->dim_m,
+ assert(!vao->vao);
+ assert(!vao->buffer);
+
+ *vao = (struct gl_vao){
+ .gl = gl,
+ .stride = stride,
+ .entries = entries,
+ .num_entries = num_entries,
};
-}
-
-// Create a texture and a FBO using the texture as color attachments.
-// fmt: texture internal format
-// If the parameters are the same as the previous call, do not touch it.
-// flags can be 0, or a combination of FBOTEX_FUZZY_W and FBOTEX_FUZZY_H.
-// Enabling FUZZY for W or H means the w or h does not need to be exact.
-bool fbotex_change(struct fbotex *fbo, struct ra *ra, struct mp_log *log,
- int w, int h, const struct ra_format *fmt, int flags)
-{
- int lw = w, lh = h;
-
- if (fbo->tex) {
- int cw = w, ch = h;
- int rw = fbo->tex->params.w, rh = fbo->tex->params.h;
-
- if ((flags & FBOTEX_FUZZY_W) && cw < rw)
- cw = rw;
- if ((flags & FBOTEX_FUZZY_H) && ch < rh)
- ch = rh;
-
- if (rw == cw && rh == ch && fbo->tex->params.format == fmt)
- goto done;
- }
-
- if (flags & FBOTEX_FUZZY_W)
- w = MP_ALIGN_UP(w, 256);
- if (flags & FBOTEX_FUZZY_H)
- h = MP_ALIGN_UP(h, 256);
-
- mp_verbose(log, "Create FBO: %dx%d (%dx%d)\n", lw, lh, w, h);
-
- if (!fmt || !fmt->renderable || !fmt->linear_filter) {
- mp_err(log, "Format %s not supported.\n", fmt ? fmt->name : "(unset)");
- return false;
- }
- fbotex_uninit(fbo);
+ gl->GenBuffers(1, &vao->buffer);
- *fbo = (struct fbotex) {
- .ra = ra,
- };
-
- struct ra_tex_params params = {
- .dimensions = 2,
- .w = w,
- .h = h,
- .d = 1,
- .format = fmt,
- .src_linear = true,
- .render_src = true,
- .render_dst = true,
- .storage_dst = true,
- .blit_src = true,
- };
+ if (gl->BindVertexArray) {
+ gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
- fbo->tex = ra_tex_create(fbo->ra, &params);
+ gl->GenVertexArrays(1, &vao->vao);
+ gl->BindVertexArray(vao->vao);
+ gl_vao_enable_attribs(vao);
+ gl->BindVertexArray(0);
- if (!fbo->tex) {
- mp_err(log, "Error: framebuffer could not be created.\n");
- fbotex_uninit(fbo);
- return false;
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
+}
-done:
-
- fbo->lw = lw;
- fbo->lh = lh;
+void gl_vao_uninit(struct gl_vao *vao)
+{
+ GL *gl = vao->gl;
+ if (!gl)
+ return;
- fbo->fbo = (struct fbodst){
- .tex = fbo->tex,
- };
+ if (gl->DeleteVertexArrays)
+ gl->DeleteVertexArrays(1, &vao->vao);
+ gl->DeleteBuffers(1, &vao->buffer);
- return true;
+ *vao = (struct gl_vao){0};
}
-void fbotex_uninit(struct fbotex *fbo)
+static void gl_vao_bind(struct gl_vao *vao)
{
- if (fbo->ra) {
- ra_tex_free(fbo->ra, &fbo->tex);
- *fbo = (struct fbotex) {0};
+ GL *gl = vao->gl;
+
+ if (gl->BindVertexArray) {
+ gl->BindVertexArray(vao->vao);
+ } else {
+ gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
+ gl_vao_enable_attribs(vao);
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
}
}
-struct timer_pool {
- struct ra *ra;
- ra_timer *timer;
- bool running; // detect invalid usage
-
- uint64_t samples[VO_PERF_SAMPLE_COUNT];
- int sample_idx;
- int sample_count;
-
- uint64_t sum;
- uint64_t peak;
-};
-
-struct timer_pool *timer_pool_create(struct ra *ra)
+static void gl_vao_unbind(struct gl_vao *vao)
{
- if (!ra->fns->timer_create)
- return NULL;
-
- ra_timer *timer = ra->fns->timer_create(ra);
- if (!timer)
- return NULL;
+ GL *gl = vao->gl;
- struct timer_pool *pool = talloc(NULL, struct timer_pool);
- if (!pool) {
- ra->fns->timer_destroy(ra, timer);
- return NULL;
+ if (gl->BindVertexArray) {
+ gl->BindVertexArray(0);
+ } else {
+ for (int n = 0; n < vao->num_entries; n++)
+ gl->DisableVertexAttribArray(n);
}
-
- *pool = (struct timer_pool){ .ra = ra, .timer = timer };
- return pool;
}
-void timer_pool_destroy(struct timer_pool *pool)
+// Draw the vertex data (as described by the gl_vao_entry entries) in ptr
+// to the screen. num is the number of vertexes. prim is usually GL_TRIANGLES.
+// If ptr is NULL, then skip the upload, and use the data uploaded with the
+// previous call.
+void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num)
{
- if (!pool)
- return;
+ GL *gl = vao->gl;
- pool->ra->fns->timer_destroy(pool->ra, pool->timer);
- talloc_free(pool);
-}
+ if (ptr) {
+ gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
+ gl->BufferData(GL_ARRAY_BUFFER, num * vao->stride, ptr, GL_STREAM_DRAW);
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
+ }
-void timer_pool_start(struct timer_pool *pool)
-{
- if (!pool)
- return;
+ gl_vao_bind(vao);
+
+ gl->DrawArrays(prim, 0, num);
- assert(!pool->running);
- pool->ra->fns->timer_start(pool->ra, pool->timer);
- pool->running = true;
+ gl_vao_unbind(vao);
}
-void timer_pool_stop(struct timer_pool *pool)
+static void GLAPIENTRY gl_debug_cb(GLenum source, GLenum type, GLuint id,
+ GLenum severity, GLsizei length,
+ const GLchar *message, const void *userParam)
{
- if (!pool)
- return;
-
- assert(pool->running);
- uint64_t res = pool->ra->fns->timer_stop(pool->ra, pool->timer);
- pool->running = false;
-
- if (res) {
- // Input res into the buffer and grab the previous value
- uint64_t old = pool->samples[pool->sample_idx];
- pool->sample_count = MPMIN(pool->sample_count + 1, VO_PERF_SAMPLE_COUNT);
- pool->samples[pool->sample_idx++] = res;
- pool->sample_idx %= VO_PERF_SAMPLE_COUNT;
- pool->sum = pool->sum + res - old;
-
- // Update peak if necessary
- if (res >= pool->peak) {
- pool->peak = res;
- } else if (pool->peak == old) {
- // It's possible that the last peak was the value we just removed,
- // if so we need to scan for the new peak
- uint64_t peak = res;
- for (int i = 0; i < VO_PERF_SAMPLE_COUNT; i++)
- peak = MPMAX(peak, pool->samples[i]);
- pool->peak = peak;
- }
+ // keep in mind that the debug callback can be asynchronous
+ struct mp_log *log = (void *)userParam;
+ int level = MSGL_ERR;
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_NOTIFICATION:level = MSGL_V; break;
+ case GL_DEBUG_SEVERITY_LOW: level = MSGL_INFO; break;
+ case GL_DEBUG_SEVERITY_MEDIUM: level = MSGL_WARN; break;
+ case GL_DEBUG_SEVERITY_HIGH: level = MSGL_ERR; break;
}
+ mp_msg(log, level, "GL: %s\n", message);
}
-struct mp_pass_perf timer_pool_measure(struct timer_pool *pool)
+void gl_set_debug_logger(GL *gl, struct mp_log *log)
{
- if (!pool)
- return (struct mp_pass_perf){0};
-
- struct mp_pass_perf res = {
- .peak = pool->peak,
- .count = pool->sample_count,
- };
-
- int idx = pool->sample_idx - pool->sample_count + VO_PERF_SAMPLE_COUNT;
- for (int i = 0; i < res.count; i++) {
- idx %= VO_PERF_SAMPLE_COUNT;
- res.samples[i] = pool->samples[idx++];
- }
-
- if (res.count > 0) {
- res.last = res.samples[res.count - 1];
- res.avg = pool->sum / res.count;
- }
-
- return res;
+ if (gl->DebugMessageCallback)
+ gl->DebugMessageCallback(log ? gl_debug_cb : NULL, log);
}
-void mp_log_source(struct mp_log *log, int lev, const char *src)
+// Given a GL combined extension string in extensions, find out whether ext
+// is included in it. Basically, a word search.
+bool gl_check_extension(const char *extensions, const char *ext)
{
- int line = 1;
- if (!src)
- return;
- while (*src) {
- const char *end = strchr(src, '\n');
- const char *next = end + 1;
- if (!end)
- next = end = src + strlen(src);
- mp_msg(log, lev, "[%3d] %.*s\n", line, (int)(end - src), src);
- line++;
- src = next;
+ int len = strlen(ext);
+ const char *cur = extensions;
+ while (cur) {
+ cur = strstr(cur, ext);
+ if (!cur)
+ break;
+ if ((cur == extensions || cur[-1] == ' ') &&
+ (cur[len] == '\0' || cur[len] == ' '))
+ return true;
+ cur += len;
}
+ return false;
}
diff --git a/video/out/opengl/utils.h b/video/out/opengl/utils.h
index 7d00d26..53127e4 100644
--- a/video/out/opengl/utils.h
+++ b/video/out/opengl/utils.h
@@ -1,121 +1,56 @@
-#pragma once
+/*
+ * This file is part of mpv.
+ * Parts based on MPlayer code by Reimar Döffinger.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_GL_UTILS_
+#define MP_GL_UTILS_
-#include <stdbool.h>
#include <math.h>
-#include "video/out/vo.h"
-#include "ra.h"
+#include "video/out/gpu/utils.h"
+#include "common.h"
-// A 3x2 matrix, with the translation part separate.
-struct gl_transform {
- // row-major, e.g. in mathematical notation:
- // | m[0][0] m[0][1] |
- // | m[1][0] m[1][1] |
- float m[2][2];
- float t[2];
-};
-
-static const struct gl_transform identity_trans = {
- .m = {{1.0, 0.0}, {0.0, 1.0}},
- .t = {0.0, 0.0},
-};
-
-void gl_transform_ortho(struct gl_transform *t, float x0, float x1,
- float y0, float y1);
-
-// This treats m as an affine transformation, in other words m[2][n] gets
-// added to the output.
-static inline void gl_transform_vec(struct gl_transform t, float *x, float *y)
-{
- float vx = *x, vy = *y;
- *x = vx * t.m[0][0] + vy * t.m[0][1] + t.t[0];
- *y = vx * t.m[1][0] + vy * t.m[1][1] + t.t[1];
-}
-
-struct mp_rect_f {
- float x0, y0, x1, y1;
-};
-
-// Semantic equality (fuzzy comparison)
-static inline bool mp_rect_f_seq(struct mp_rect_f a, struct mp_rect_f b)
-{
- return fabs(a.x0 - b.x0) < 1e-6 && fabs(a.x1 - b.x1) < 1e-6 &&
- fabs(a.y0 - b.y0) < 1e-6 && fabs(a.y1 - b.y1) < 1e-6;
-}
-
-static inline void gl_transform_rect(struct gl_transform t, struct mp_rect_f *r)
-{
- gl_transform_vec(t, &r->x0, &r->y0);
- gl_transform_vec(t, &r->x1, &r->y1);
-}
-
-static inline bool gl_transform_eq(struct gl_transform a, struct gl_transform b)
-{
- for (int x = 0; x < 2; x++) {
- for (int y = 0; y < 2; y++) {
- if (a.m[x][y] != b.m[x][y])
- return false;
- }
- }
-
- return a.t[0] == b.t[0] && a.t[1] == b.t[1];
-}
-
-void gl_transform_trans(struct gl_transform t, struct gl_transform *x);
-
-struct fbodst {
- struct ra_tex *tex;
- bool flip; // mirror vertically
-};
-
-void gl_transform_ortho_fbodst(struct gl_transform *t, struct fbodst fbo);
-
-// A pool of buffers, which can grow as needed
-struct ra_buf_pool {
- struct ra_buf_params current_params;
- struct ra_buf **buffers;
- int num_buffers;
- int index;
-};
-
-void ra_buf_pool_uninit(struct ra *ra, struct ra_buf_pool *pool);
+struct mp_log;
-// Note: params->initial_data is *not* supported
-struct ra_buf *ra_buf_pool_get(struct ra *ra, struct ra_buf_pool *pool,
- const struct ra_buf_params *params);
+void gl_check_error(GL *gl, struct mp_log *log, const char *info);
-// Helper that wraps ra_tex_upload using texture upload buffers to ensure that
-// params->buf is always set. This is intended for RA-internal usage.
-bool ra_tex_upload_pbo(struct ra *ra, struct ra_buf_pool *pbo,
- const struct ra_tex_upload_params *params);
+void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
+ const void *dataptr, int stride,
+ int x, int y, int w, int h);
-// Layout rules for GLSL's packing modes
-struct ra_layout std140_layout(struct ra_renderpass_input *inp);
-struct ra_layout std430_layout(struct ra_renderpass_input *inp);
+mp_image_t *gl_read_fbo_contents(GL *gl, int fbo, int w, int h);
-struct fbotex {
- struct ra *ra;
- struct ra_tex *tex;
- int lw, lh; // logical (configured) size, <= than texture size
- struct fbodst fbo;
+struct gl_vao {
+ GL *gl;
+ GLuint vao; // the VAO object, or 0 if unsupported by driver
+ GLuint buffer; // GL_ARRAY_BUFFER used for the data
+ int stride; // size of each element (interleaved elements are assumed)
+ const struct ra_renderpass_input *entries;
+ int num_entries;
};
-void fbotex_uninit(struct fbotex *fbo);
-bool fbotex_change(struct fbotex *fbo, struct ra *ra, struct mp_log *log,
- int w, int h, const struct ra_format *fmt, int flags);
-#define FBOTEX_FUZZY_W 1
-#define FBOTEX_FUZZY_H 2
-#define FBOTEX_FUZZY (FBOTEX_FUZZY_W | FBOTEX_FUZZY_H)
+void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
+ const struct ra_renderpass_input *entries,
+ int num_entries);
+void gl_vao_uninit(struct gl_vao *vao);
+void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num);
-// A wrapper around ra_timer that does result pooling, averaging etc.
-struct timer_pool;
+void gl_set_debug_logger(GL *gl, struct mp_log *log);
-struct timer_pool *timer_pool_create(struct ra *ra);
-void timer_pool_destroy(struct timer_pool *pool);
-void timer_pool_start(struct timer_pool *pool);
-void timer_pool_stop(struct timer_pool *pool);
-struct mp_pass_perf timer_pool_measure(struct timer_pool *pool);
+bool gl_check_extension(const char *extensions, const char *ext);
-// print a multi line string with line numbers (e.g. for shader sources)
-// log, lev: module and log level, as in mp_msg()
-void mp_log_source(struct mp_log *log, int lev, const char *src);
+#endif
diff --git a/video/out/vo.c b/video/out/vo.c
index f9c5d04..63f5b34 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -47,10 +47,11 @@
#include "osdep/io.h"
#include "osdep/threads.h"
+extern const struct vo_driver video_out_mediacodec_embed;
extern const struct vo_driver video_out_x11;
extern const struct vo_driver video_out_vdpau;
extern const struct vo_driver video_out_xv;
-extern const struct vo_driver video_out_opengl;
+extern const struct vo_driver video_out_gpu;
extern const struct vo_driver video_out_opengl_cb;
extern const struct vo_driver video_out_null;
extern const struct vo_driver video_out_image;
@@ -60,34 +61,31 @@ extern const struct vo_driver video_out_drm;
extern const struct vo_driver video_out_direct3d;
extern const struct vo_driver video_out_sdl;
extern const struct vo_driver video_out_vaapi;
-extern const struct vo_driver video_out_wayland;
extern const struct vo_driver video_out_rpi;
extern const struct vo_driver video_out_tct;
const struct vo_driver *const video_out_drivers[] =
{
+#if HAVE_ANDROID
+ &video_out_mediacodec_embed,
+#endif
#if HAVE_RPI
&video_out_rpi,
#endif
-#if HAVE_GL
- &video_out_opengl,
-#endif
+ &video_out_gpu,
#if HAVE_VDPAU
&video_out_vdpau,
#endif
#if HAVE_DIRECT3D
&video_out_direct3d,
#endif
-#if HAVE_WAYLAND
- &video_out_wayland,
-#endif
#if HAVE_XV
&video_out_xv,
#endif
#if HAVE_SDL2
&video_out_sdl,
#endif
-#if HAVE_VAAPI_X11
+#if HAVE_VAAPI_X11 && HAVE_GPL
&video_out_vaapi,
#endif
#if HAVE_X11
@@ -136,6 +134,8 @@ struct vo_internal {
int64_t nominal_vsync_interval;
+ bool external_renderloop_drive;
+
int64_t vsync_interval;
int64_t *vsync_samples;
int num_vsync_samples;
@@ -196,8 +196,9 @@ const struct m_obj_list vo_obj_list = {
.get_desc = get_desc,
.description = "video outputs",
.aliases = {
- {"gl", "opengl"},
+ {"gl", "gpu"},
{"direct3d_shaders", "direct3d"},
+ {"opengl", "gpu"},
{0}
},
.allow_unknown_entries = true,
@@ -789,11 +790,12 @@ static void wait_until(struct vo *vo, int64_t target)
pthread_mutex_unlock(&in->lock);
}
-static bool render_frame(struct vo *vo)
+bool vo_render_frame_external(struct vo *vo)
{
struct vo_internal *in = vo->in;
struct vo_frame *frame = NULL;
bool got_frame = false;
+ bool flipped = false;
update_display_fps(vo);
@@ -855,6 +857,7 @@ static bool render_frame(struct vo *vo)
if (in->dropped_frame) {
in->drop_count += 1;
} else {
+ flipped = true;
in->rendering = true;
in->hasframe_rendered = true;
int64_t prev_drop_count = vo->in->drop_count;
@@ -886,6 +889,11 @@ static bool render_frame(struct vo *vo)
update_vsync_timing_after_swap(vo);
}
+ if (vo->driver->caps & VO_CAP_NOREDRAW) {
+ talloc_free(in->current_frame);
+ in->current_frame = NULL;
+ }
+
if (in->dropped_frame) {
MP_STATS(vo, "drop-vo");
} else {
@@ -900,6 +908,8 @@ static bool render_frame(struct vo *vo)
done:
talloc_free(frame);
pthread_mutex_unlock(&in->lock);
+ if (in->external_renderloop_drive)
+ return flipped;
return got_frame || (in->frame_queued && in->frame_queued->display_synced);
}
@@ -907,7 +917,7 @@ static void do_redraw(struct vo *vo)
{
struct vo_internal *in = vo->in;
- if (!vo->config_ok)
+ if (!vo->config_ok || (vo->driver->caps & VO_CAP_NOREDRAW))
return;
pthread_mutex_lock(&in->lock);
@@ -942,6 +952,44 @@ static void do_redraw(struct vo *vo)
talloc_free(frame);
}
+static void drop_unrendered_frame(struct vo *vo)
+{
+ struct vo_internal *in = vo->in;
+
+ pthread_mutex_lock(&in->lock);
+
+ if (!in->frame_queued)
+ goto end;
+
+ if ((in->frame_queued->pts + in->frame_queued->duration) > mp_time_us())
+ goto end;
+
+ MP_VERBOSE(vo, "Dropping unrendered frame (pts %"PRId64")\n", in->frame_queued->pts);
+
+ talloc_free(in->frame_queued);
+ in->frame_queued = NULL;
+ in->hasframe = false;
+ pthread_cond_broadcast(&in->wakeup);
+ wakeup_core(vo);
+
+end:
+ pthread_mutex_unlock(&in->lock);
+}
+
+void vo_enable_external_renderloop(struct vo *vo)
+{
+ struct vo_internal *in = vo->in;
+ MP_VERBOSE(vo, "Enabling event driven renderloop!\n");
+ in->external_renderloop_drive = true;
+}
+
+void vo_disable_external_renderloop(struct vo *vo)
+{
+ struct vo_internal *in = vo->in;
+ MP_VERBOSE(vo, "Disabling event driven renderloop!\n");
+ in->external_renderloop_drive = false;
+}
+
static void *vo_thread(void *ptr)
{
struct vo *vo = ptr;
@@ -963,7 +1011,11 @@ static void *vo_thread(void *ptr)
if (in->terminate)
break;
vo->driver->control(vo, VOCTRL_CHECK_EVENTS, NULL);
- bool working = render_frame(vo);
+ bool working = false;
+ if (!in->external_renderloop_drive || !in->hasframe_rendered)
+ working = vo_render_frame_external(vo);
+ else
+ drop_unrendered_frame(vo);
int64_t now = mp_time_us();
int64_t wait_until = now + (working ? 0 : (int64_t)1e9);
@@ -976,7 +1028,7 @@ static void *vo_thread(void *ptr)
wakeup_core(vo);
}
}
- if (vo->want_redraw && !in->want_redraw) {
+ if (vo->want_redraw) {
vo->want_redraw = false;
in->want_redraw = true;
wakeup_core(vo);
diff --git a/video/out/vo.h b/video/out/vo.h
index 2a0c3ef..995d6b9 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -172,6 +172,8 @@ enum {
VO_CAP_ROTATE90 = 1 << 0,
// VO does framedrop itself (vo_vdpau). Untimed/encoding VOs never drop.
VO_CAP_FRAMEDROP = 1 << 1,
+ // VO does not support redraws (vo_mediacodec_embed).
+ VO_CAP_NOREDRAW = 1 << 2,
};
#define VO_MAX_REQ_FRAMES 10
@@ -374,7 +376,7 @@ struct vo {
struct vo_x11_state *x11;
struct vo_w32_state *w32;
struct vo_cocoa_state *cocoa;
- struct vo_wayland_state *wayland;
+ struct vo_wayland_state *wl;
struct mp_hwdec_devices *hwdec_devs;
struct input_ctx *input_ctx;
struct osd_state *osd;
@@ -431,6 +433,9 @@ void vo_query_formats(struct vo *vo, uint8_t *list);
void vo_event(struct vo *vo, int event);
int vo_query_and_reset_events(struct vo *vo, int events);
struct mp_image *vo_get_current_frame(struct vo *vo);
+void vo_enable_external_renderloop(struct vo *vo);
+void vo_disable_external_renderloop(struct vo *vo);
+bool vo_render_frame_external(struct vo *vo);
void vo_set_queue_params(struct vo *vo, int64_t offset_us, int num_req_frames);
int vo_get_num_req_frames(struct vo *vo);
int64_t vo_get_vsync_interval(struct vo *vo);
diff --git a/video/out/vo_caca.c b/video/out/vo_caca.c
index 46090af..e63bd69 100644
--- a/video/out/vo_caca.c
+++ b/video/out/vo_caca.c
@@ -42,6 +42,11 @@
#include "common/msg.h"
#include "input/input.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
struct priv {
caca_canvas_t *canvas;
caca_display_t *display;
diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c
index 952dca8..a131d21 100644
--- a/video/out/vo_direct3d.c
+++ b/video/out/vo_direct3d.c
@@ -40,6 +40,11 @@
#include "w32_common.h"
#include "sub/osd.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
// shaders generated by fxc.exe from d3d_shader_yuv.hlsl
#include "d3d_shader_420p.h"
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c
index 2fdd840..24189d5 100644
--- a/video/out/vo_drm.c
+++ b/video/out/vo_drm.c
@@ -412,7 +412,9 @@ static int preinit(struct vo *vo)
}
p->kms = kms_create(
- vo->log, vo->opts->drm_connector_spec, vo->opts->drm_mode_id);
+ vo->log, vo->opts->drm_opts->drm_connector_spec,
+ vo->opts->drm_opts->drm_mode_id,
+ vo->opts->drm_opts->drm_overlay_id);
if (!p->kms) {
MP_ERR(vo, "Failed to create KMS.\n");
goto err;
diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c
new file mode 100644
index 0000000..95318d3
--- /dev/null
+++ b/video/out/vo_gpu.c
@@ -0,0 +1,336 @@
+/*
+ * Based on vo_gl.c by Reimar Doeffinger.
+ *
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+
+#include "config.h"
+
+#include "mpv_talloc.h"
+#include "common/common.h"
+#include "misc/bstr.h"
+#include "common/msg.h"
+#include "common/global.h"
+#include "options/m_config.h"
+#include "vo.h"
+#include "video/mp_image.h"
+#include "sub/osd.h"
+
+#include "gpu/context.h"
+#include "gpu/hwdec.h"
+#include "gpu/video.h"
+
+struct gpu_priv {
+ struct mp_log *log;
+ struct ra_ctx *ctx;
+
+ char *context_name;
+ char *context_type;
+ struct ra_ctx_opts opts;
+ struct gl_video *renderer;
+
+ int events;
+};
+
+static void resize(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+ struct ra_swapchain *sw = p->ctx->swapchain;
+
+ MP_VERBOSE(vo, "Resize: %dx%d\n", vo->dwidth, vo->dheight);
+
+ struct mp_rect src, dst;
+ struct mp_osd_res osd;
+ vo_get_src_dst_rects(vo, &src, &dst, &osd);
+
+ gl_video_resize(p->renderer, &src, &dst, &osd);
+
+ int fb_depth = sw->fns->color_depth ? sw->fns->color_depth(sw) : 0;
+ if (fb_depth)
+ MP_VERBOSE(p, "Reported display depth: %d\n", fb_depth);
+ gl_video_set_fb_depth(p->renderer, fb_depth);
+
+ vo->want_redraw = true;
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
+{
+ struct gpu_priv *p = vo->priv;
+ struct ra_swapchain *sw = p->ctx->swapchain;
+
+ struct ra_fbo fbo;
+ if (!sw->fns->start_frame(sw, &fbo))
+ return;
+
+ gl_video_render_frame(p->renderer, frame, fbo);
+ if (!sw->fns->submit_frame(sw, frame)) {
+ MP_ERR(vo, "Failed presenting frame!\n");
+ return;
+ }
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+ struct ra_swapchain *sw = p->ctx->swapchain;
+ sw->fns->swap_buffers(sw);
+}
+
+static int query_format(struct vo *vo, int format)
+{
+ struct gpu_priv *p = vo->priv;
+ if (!gl_video_check_format(p->renderer, format))
+ return 0;
+ return 1;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ struct gpu_priv *p = vo->priv;
+
+ if (!p->ctx->fns->reconfig(p->ctx))
+ return -1;
+
+ resize(vo);
+ gl_video_config(p->renderer, params);
+
+ return 0;
+}
+
+static void request_hwdec_api(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+
+ gl_video_load_hwdecs_all(p->renderer, vo->hwdec_devs);
+}
+
+static void call_request_hwdec_api(void *ctx)
+{
+ // Roundabout way to run hwdec loading on the VO thread.
+ // Redirects to request_hwdec_api().
+ vo_control(ctx, VOCTRL_LOAD_HWDEC_API, NULL);
+}
+
+static void get_and_update_icc_profile(struct gpu_priv *p)
+{
+ if (gl_video_icc_auto_enabled(p->renderer)) {
+ MP_VERBOSE(p, "Querying ICC profile...\n");
+ bstr icc = bstr0(NULL);
+ int r = p->ctx->fns->control(p->ctx, &p->events, VOCTRL_GET_ICC_PROFILE, &icc);
+
+ if (r != VO_NOTAVAIL) {
+ if (r == VO_FALSE) {
+ MP_WARN(p, "Could not retrieve an ICC profile.\n");
+ } else if (r == VO_NOTIMPL) {
+ MP_ERR(p, "icc-profile-auto not implemented on this platform.\n");
+ }
+
+ gl_video_set_icc_profile(p->renderer, icc);
+ }
+ }
+}
+
+static void get_and_update_ambient_lighting(struct gpu_priv *p)
+{
+ int lux;
+ int r = p->ctx->fns->control(p->ctx, &p->events, VOCTRL_GET_AMBIENT_LUX, &lux);
+ if (r == VO_TRUE) {
+ gl_video_set_ambient_lux(p->renderer, lux);
+ }
+ if (r != VO_TRUE && gl_video_gamma_auto_enabled(p->renderer)) {
+ MP_ERR(p, "gamma_auto option provided, but querying for ambient"
+ " lighting is not supported on this platform\n");
+ }
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct gpu_priv *p = vo->priv;
+ struct ra_swapchain *sw = p->ctx->swapchain;
+
+ switch (request) {
+ case VOCTRL_SET_PANSCAN:
+ resize(vo);
+ return VO_TRUE;
+ case VOCTRL_SET_EQUALIZER:
+ vo->want_redraw = true;
+ return VO_TRUE;
+ case VOCTRL_SCREENSHOT_WIN: {
+ struct mp_image *screen = NULL;
+ if (sw->fns->screenshot)
+ screen = sw->fns->screenshot(sw);
+ if (!screen)
+ break; // redirect to backend
+ // set image parameters according to the display, if possible
+ screen->params.color = gl_video_get_output_colorspace(p->renderer);
+ *(struct mp_image **)data = screen;
+ return true;
+ }
+ case VOCTRL_LOAD_HWDEC_API:
+ request_hwdec_api(vo);
+ return true;
+ case VOCTRL_UPDATE_RENDER_OPTS: {
+ gl_video_configure_queue(p->renderer, vo);
+ get_and_update_icc_profile(p);
+ vo->want_redraw = true;
+ return true;
+ }
+ case VOCTRL_RESET:
+ gl_video_reset(p->renderer);
+ return true;
+ case VOCTRL_PAUSE:
+ if (gl_video_showing_interpolated_frame(p->renderer))
+ vo->want_redraw = true;
+ break;
+ case VOCTRL_PERFORMANCE_DATA:
+ gl_video_perfdata(p->renderer, (struct voctrl_performance_data *)data);
+ return true;
+ }
+
+ int events = 0;
+ int r = p->ctx->fns->control(p->ctx, &events, request, data);
+ if (events & VO_EVENT_ICC_PROFILE_CHANGED) {
+ get_and_update_icc_profile(p);
+ vo->want_redraw = true;
+ }
+ if (events & VO_EVENT_AMBIENT_LIGHTING_CHANGED) {
+ get_and_update_ambient_lighting(p);
+ vo->want_redraw = true;
+ }
+ events |= p->events;
+ p->events = 0;
+ if (events & VO_EVENT_RESIZE)
+ resize(vo);
+ if (events & VO_EVENT_EXPOSE)
+ vo->want_redraw = true;
+ vo_event(vo, events);
+
+ return r;
+}
+
+static void wakeup(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+ if (p->ctx && p->ctx->fns->wakeup)
+ p->ctx->fns->wakeup(p->ctx);
+}
+
+static void wait_events(struct vo *vo, int64_t until_time_us)
+{
+ struct gpu_priv *p = vo->priv;
+ if (p->ctx && p->ctx->fns->wait_events) {
+ p->ctx->fns->wait_events(p->ctx, until_time_us);
+ } else {
+ vo_wait_default(vo, until_time_us);
+ }
+}
+
+static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
+ int stride_align)
+{
+ struct gpu_priv *p = vo->priv;
+
+ return gl_video_get_image(p->renderer, imgfmt, w, h, stride_align);
+}
+
+static void uninit(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+
+ gl_video_uninit(p->renderer);
+ if (vo->hwdec_devs) {
+ hwdec_devices_set_loader(vo->hwdec_devs, NULL, NULL);
+ hwdec_devices_destroy(vo->hwdec_devs);
+ }
+ ra_ctx_destroy(&p->ctx);
+}
+
+static int preinit(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+ p->log = vo->log;
+
+ int alpha_mode;
+ mp_read_option_raw(vo->global, "alpha", &m_option_type_choice, &alpha_mode);
+
+ struct ra_ctx_opts opts = p->opts;
+ opts.want_alpha = alpha_mode == 1;
+
+ p->ctx = ra_ctx_create(vo, p->context_type, p->context_name, opts);
+ if (!p->ctx)
+ goto err_out;
+ assert(p->ctx->ra);
+ assert(p->ctx->swapchain);
+
+ p->renderer = gl_video_init(p->ctx->ra, vo->log, vo->global);
+ gl_video_set_osd_source(p->renderer, vo->osd);
+ gl_video_configure_queue(p->renderer, vo);
+
+ get_and_update_icc_profile(p);
+
+ vo->hwdec_devs = hwdec_devices_create();
+ hwdec_devices_set_loader(vo->hwdec_devs, call_request_hwdec_api, vo);
+
+ gl_video_load_hwdecs(p->renderer, vo->hwdec_devs, false);
+
+ return 0;
+
+err_out:
+ uninit(vo);
+ return -1;
+}
+
+#define OPT_BASE_STRUCT struct gpu_priv
+static const m_option_t options[] = {
+ OPT_STRING_VALIDATE("gpu-context", context_name, 0, ra_ctx_validate_context),
+ OPT_STRING_VALIDATE("gpu-api", context_type, 0, ra_ctx_validate_api),
+ OPT_FLAG("gpu-debug", opts.debug, 0),
+ OPT_FLAG("gpu-sw", opts.allow_sw, 0),
+ OPT_INTRANGE("swapchain-depth", opts.swapchain_depth, 0, 1, 8),
+ {0}
+};
+
+static const struct gpu_priv defaults = { .opts = {
+ .swapchain_depth = 3,
+}};
+
+const struct vo_driver video_out_gpu = {
+ .description = "Shader-based GPU Renderer",
+ .name = "gpu",
+ .caps = VO_CAP_ROTATE90,
+ .preinit = preinit,
+ .query_format = query_format,
+ .reconfig = reconfig,
+ .control = control,
+ .get_image = get_image,
+ .draw_frame = draw_frame,
+ .flip_page = flip_page,
+ .wait_events = wait_events,
+ .wakeup = wakeup,
+ .uninit = uninit,
+ .priv_size = sizeof(struct gpu_priv),
+ .priv_defaults = &defaults,
+ .options = options,
+};
diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c
index be7de12..4b69231 100644
--- a/video/out/vo_lavc.c
+++ b/video/out/vo_lavc.c
@@ -49,7 +49,6 @@ struct priv {
int64_t mindeltapts;
double expected_next_pts;
mp_image_t *lastimg;
- int lastimg_wants_osd;
int lastdisplaycount;
AVRational worst_time_base;
@@ -287,6 +286,14 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
double pts = mpi ? mpi->pts : MP_NOPTS_VALUE;
+ if (mpi) {
+ assert(vo->params);
+
+ struct mp_osd_res dim = osd_res_from_image_params(vo->params);
+
+ osd_draw_on_image(vo->osd, dim, mpi->pts, OSD_DRAW_SUB_ONLY, mpi);
+ }
+
if (!vc || vc->shutdown)
goto done;
if (!encode_lavc_start(ectx)) {
@@ -451,7 +458,6 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
talloc_free(vc->lastimg);
vc->lastimg = mpi;
mpi = NULL;
- vc->lastimg_wants_osd = true;
vc->lastframeipts = vc->lastipts = frameipts;
if (ectx->options->rawts && vc->lastipts < 0) {
@@ -462,17 +468,9 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
} else {
MP_INFO(vo, "Frame at pts %d got dropped "
"entirely because pts went backwards\n", (int) frameipts);
- vc->lastimg_wants_osd = false;
}
}
- if (vc->lastimg && vc->lastimg_wants_osd && vo->params) {
- struct mp_osd_res dim = osd_res_from_image_params(vo->params);
-
- osd_draw_on_image(vo->osd, dim, vc->lastimg->pts, OSD_DRAW_SUB_ONLY,
- vc->lastimg);
- }
-
done:
talloc_free(mpi);
}
diff --git a/video/out/vo_mediacodec_embed.c b/video/out/vo_mediacodec_embed.c
new file mode 100644
index 0000000..63975e9
--- /dev/null
+++ b/video/out/vo_mediacodec_embed.c
@@ -0,0 +1,119 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libavcodec/mediacodec.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_mediacodec.h>
+
+#include "common/common.h"
+#include "vo.h"
+#include "video/mp_image.h"
+#include "video/hwdec.h"
+
+struct priv {
+ struct mp_image *next_image;
+ struct mp_hwdec_ctx hwctx;
+};
+
+static AVBufferRef *create_mediacodec_device_ref(struct vo *vo)
+{
+ AVBufferRef *device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_MEDIACODEC);
+ if (!device_ref)
+ return NULL;
+
+ AVHWDeviceContext *ctx = (void *)device_ref->data;
+ AVMediaCodecDeviceContext *hwctx = ctx->hwctx;
+ hwctx->surface = (void *)(intptr_t)(vo->opts->WinID);
+
+ if (av_hwdevice_ctx_init(device_ref) < 0)
+ av_buffer_unref(&device_ref);
+
+ return device_ref;
+}
+
+static int preinit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ vo->hwdec_devs = hwdec_devices_create();
+ p->hwctx = (struct mp_hwdec_ctx){
+ .driver_name = "mediacodec_embed",
+ .av_device_ref = create_mediacodec_device_ref(vo),
+ };
+ hwdec_devices_add(vo->hwdec_devs, &p->hwctx);
+ return 0;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ if (!p->next_image)
+ return;
+
+ AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *)p->next_image->planes[3];
+ av_mediacodec_release_buffer(buffer, 1);
+ mp_image_unrefp(&p->next_image);
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
+{
+ struct priv *p = vo->priv;
+
+ mp_image_t *mpi = NULL;
+ if (!frame->redraw && !frame->repeat)
+ mpi = mp_image_new_ref(frame->current);
+
+ talloc_free(p->next_image);
+ p->next_image = mpi;
+}
+
+static int query_format(struct vo *vo, int format)
+{
+ return format == IMGFMT_MEDIACODEC;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ return VO_NOTIMPL;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ return 0;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ mp_image_unrefp(&p->next_image);
+
+ hwdec_devices_remove(vo->hwdec_devs, &p->hwctx);
+ av_buffer_unref(&p->hwctx.av_device_ref);
+}
+
+const struct vo_driver video_out_mediacodec_embed = {
+ .description = "Android (Embedded MediaCodec Surface)",
+ .name = "mediacodec_embed",
+ .caps = VO_CAP_NOREDRAW,
+ .preinit = preinit,
+ .query_format = query_format,
+ .control = control,
+ .draw_frame = draw_frame,
+ .flip_page = flip_page,
+ .reconfig = reconfig,
+ .uninit = uninit,
+ .priv_size = sizeof(struct priv),
+};
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
deleted file mode 100644
index 72691e5..0000000
--- a/video/out/vo_opengl.c
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Based on vo_gl.c by Reimar Doeffinger.
- *
- * This file is part of mpv.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-#include <stdbool.h>
-#include <assert.h>
-
-#include <libavutil/common.h>
-
-#include "config.h"
-
-#include "mpv_talloc.h"
-#include "common/common.h"
-#include "misc/bstr.h"
-#include "common/msg.h"
-#include "common/global.h"
-#include "options/m_config.h"
-#include "vo.h"
-#include "video/mp_image.h"
-#include "sub/osd.h"
-
-#include "opengl/context.h"
-#include "opengl/utils.h"
-#include "opengl/hwdec.h"
-#include "opengl/osd.h"
-#include "filter_kernels.h"
-#include "video/hwdec.h"
-#include "opengl/video.h"
-#include "opengl/ra_gl.h"
-
-#define NUM_VSYNC_FENCES 10
-
-struct vo_opengl_opts {
- int use_glFinish;
- int waitvsync;
- int use_gl_debug;
- int allow_sw;
- int swap_interval;
- int vsync_fences;
- char *backend;
- int es;
- int pattern[2];
-};
-
-struct gl_priv {
- struct vo *vo;
- struct mp_log *log;
- MPGLContext *glctx;
- GL *gl;
- struct ra *ra;
-
- struct vo_opengl_opts opts;
-
- struct gl_video *renderer;
-
- struct ra_hwdec *hwdec;
-
- int events;
-
- int frames_rendered;
- unsigned int prev_sgi_sync_count;
-
- // check-pattern sub-option; for testing/debugging
- int last_pattern;
- int matches, mismatches;
-
- GLsync vsync_fences[NUM_VSYNC_FENCES];
- int num_vsync_fences;
-};
-
-static void resize(struct gl_priv *p)
-{
- struct vo *vo = p->vo;
-
- MP_VERBOSE(vo, "Resize: %dx%d\n", vo->dwidth, vo->dheight);
-
- struct mp_rect src, dst;
- struct mp_osd_res osd;
- vo_get_src_dst_rects(vo, &src, &dst, &osd);
-
- gl_video_resize(p->renderer, &src, &dst, &osd);
-
- vo->want_redraw = true;
-}
-
-static void check_pattern(struct vo *vo, int item)
-{
- struct gl_priv *p = vo->priv;
- int expected = p->opts.pattern[p->last_pattern];
- if (item == expected) {
- p->last_pattern++;
- if (p->last_pattern >= 2)
- p->last_pattern = 0;
- p->matches++;
- } else {
- p->mismatches++;
- MP_WARN(vo, "wrong pattern, expected %d got %d (hit: %d, mis: %d)\n",
- expected, item, p->matches, p->mismatches);
- }
-}
-
-static void draw_frame(struct vo *vo, struct vo_frame *frame)
-{
- struct gl_priv *p = vo->priv;
- GL *gl = p->gl;
-
- mpgl_start_frame(p->glctx);
-
- if (gl->FenceSync && p->num_vsync_fences < p->opts.vsync_fences) {
- GLsync fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);;
- if (fence)
- p->vsync_fences[p->num_vsync_fences++] = fence;
- }
-
- struct fbodst target = {
- .tex = ra_create_wrapped_fb(p->ra, p->glctx->main_fb,
- vo->dwidth, vo->dheight),
- .flip = !p->glctx->flip_v,
- };
- gl_video_render_frame(p->renderer, frame, target);
- ra_tex_free(p->ra, &target.tex);
-
- if (p->opts.use_glFinish)
- gl->Finish();
-}
-
-static void flip_page(struct vo *vo)
-{
- struct gl_priv *p = vo->priv;
- GL *gl = p->gl;
-
- mpgl_swap_buffers(p->glctx);
-
- p->frames_rendered++;
- if (p->frames_rendered > 5 && !p->opts.use_gl_debug)
- ra_gl_set_debug(p->ra, false);
-
- if (p->opts.use_glFinish)
- gl->Finish();
-
- if (p->opts.waitvsync || p->opts.pattern[0]) {
- if (gl->GetVideoSync) {
- unsigned int n1 = 0, n2 = 0;
- gl->GetVideoSync(&n1);
- if (p->opts.waitvsync)
- gl->WaitVideoSync(2, (n1 + 1) % 2, &n2);
- int step = n1 - p->prev_sgi_sync_count;
- p->prev_sgi_sync_count = n1;
- MP_DBG(vo, "Flip counts: %u->%u, step=%d\n", n1, n2, step);
- if (p->opts.pattern[0])
- check_pattern(vo, step);
- } else {
- MP_WARN(vo, "GLX_SGI_video_sync not available, disabling.\n");
- p->opts.waitvsync = 0;
- p->opts.pattern[0] = 0;
- }
- }
- while (p->opts.vsync_fences > 0 && p->num_vsync_fences >= p->opts.vsync_fences) {
- gl->ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9);
- gl->DeleteSync(p->vsync_fences[0]);
- MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0);
- }
-}
-
-static int query_format(struct vo *vo, int format)
-{
- struct gl_priv *p = vo->priv;
- if (!gl_video_check_format(p->renderer, format))
- return 0;
- return 1;
-}
-
-static int reconfig(struct vo *vo, struct mp_image_params *params)
-{
- struct gl_priv *p = vo->priv;
-
- if (mpgl_reconfig_window(p->glctx) < 0)
- return -1;
-
- resize(p);
-
- gl_video_config(p->renderer, params);
-
- return 0;
-}
-
-static void request_hwdec_api(struct vo *vo, void *api)
-{
- struct gl_priv *p = vo->priv;
-
- if (p->hwdec)
- return;
-
- p->hwdec = ra_hwdec_load_api(p->vo->log, p->ra, p->vo->global,
- vo->hwdec_devs, (intptr_t)api);
- gl_video_set_hwdec(p->renderer, p->hwdec);
-}
-
-static void call_request_hwdec_api(void *ctx, enum hwdec_type type)
-{
- // Roundabout way to run hwdec loading on the VO thread.
- // Redirects to request_hwdec_api().
- vo_control(ctx, VOCTRL_LOAD_HWDEC_API, (void *)(intptr_t)type);
-}
-
-static void get_and_update_icc_profile(struct gl_priv *p)
-{
- if (gl_video_icc_auto_enabled(p->renderer)) {
- MP_VERBOSE(p, "Querying ICC profile...\n");
- bstr icc = bstr0(NULL);
- int r = mpgl_control(p->glctx, &p->events, VOCTRL_GET_ICC_PROFILE, &icc);
-
- if (r != VO_NOTAVAIL) {
- if (r == VO_FALSE) {
- MP_WARN(p, "Could not retrieve an ICC profile.\n");
- } else if (r == VO_NOTIMPL) {
- MP_ERR(p, "icc-profile-auto not implemented on this platform.\n");
- }
-
- gl_video_set_icc_profile(p->renderer, icc);
- }
- }
-}
-
-static void get_and_update_ambient_lighting(struct gl_priv *p)
-{
- int lux;
- int r = mpgl_control(p->glctx, &p->events, VOCTRL_GET_AMBIENT_LUX, &lux);
- if (r == VO_TRUE) {
- gl_video_set_ambient_lux(p->renderer, lux);
- }
- if (r != VO_TRUE && gl_video_gamma_auto_enabled(p->renderer)) {
- MP_ERR(p, "gamma_auto option provided, but querying for ambient"
- " lighting is not supported on this platform\n");
- }
-}
-
-static int control(struct vo *vo, uint32_t request, void *data)
-{
- struct gl_priv *p = vo->priv;
-
- switch (request) {
- case VOCTRL_SET_PANSCAN:
- resize(p);
- return VO_TRUE;
- case VOCTRL_SET_EQUALIZER:
- vo->want_redraw = true;
- return VO_TRUE;
- case VOCTRL_SCREENSHOT_WIN: {
- struct mp_image *screen = gl_read_fbo_contents(p->gl, p->glctx->main_fb,
- vo->dwidth, vo->dheight);
- if (!screen)
- break; // redirect to backend
- // set image parameters according to the display, if possible
- screen->params.color = gl_video_get_output_colorspace(p->renderer);
- if (p->glctx->flip_v)
- mp_image_vflip(screen);
- *(struct mp_image **)data = screen;
- return true;
- }
- case VOCTRL_LOAD_HWDEC_API:
- request_hwdec_api(vo, data);
- return true;
- case VOCTRL_UPDATE_RENDER_OPTS: {
- gl_video_update_options(p->renderer);
- get_and_update_icc_profile(p);
- gl_video_configure_queue(p->renderer, p->vo);
- p->vo->want_redraw = true;
- return true;
- }
- case VOCTRL_RESET:
- gl_video_reset(p->renderer);
- return true;
- case VOCTRL_PAUSE:
- if (gl_video_showing_interpolated_frame(p->renderer))
- vo->want_redraw = true;
- return true;
- case VOCTRL_PERFORMANCE_DATA:
- gl_video_perfdata(p->renderer, (struct voctrl_performance_data *)data);
- return true;
- }
-
- int events = 0;
- int r = mpgl_control(p->glctx, &events, request, data);
- if (events & VO_EVENT_ICC_PROFILE_CHANGED) {
- get_and_update_icc_profile(p);
- vo->want_redraw = true;
- }
- if (events & VO_EVENT_AMBIENT_LIGHTING_CHANGED) {
- get_and_update_ambient_lighting(p);
- vo->want_redraw = true;
- }
- events |= p->events;
- p->events = 0;
- if (events & VO_EVENT_RESIZE)
- resize(p);
- if (events & VO_EVENT_EXPOSE)
- vo->want_redraw = true;
- vo_event(vo, events);
-
- return r;
-}
-
-static void wakeup(struct vo *vo)
-{
- struct gl_priv *p = vo->priv;
- if (p->glctx && p->glctx->driver->wakeup)
- p->glctx->driver->wakeup(p->glctx);
-}
-
-static void wait_events(struct vo *vo, int64_t until_time_us)
-{
- struct gl_priv *p = vo->priv;
- if (p->glctx->driver->wait_events) {
- p->glctx->driver->wait_events(p->glctx, until_time_us);
- } else {
- vo_wait_default(vo, until_time_us);
- }
-}
-
-static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
- int stride_align)
-{
- struct gl_priv *p = vo->priv;
-
- return gl_video_get_image(p->renderer, imgfmt, w, h, stride_align);
-}
-
-static void uninit(struct vo *vo)
-{
- struct gl_priv *p = vo->priv;
-
- gl_video_uninit(p->renderer);
- ra_hwdec_uninit(p->hwdec);
- if (vo->hwdec_devs) {
- hwdec_devices_set_loader(vo->hwdec_devs, NULL, NULL);
- hwdec_devices_destroy(vo->hwdec_devs);
- }
- ra_free(&p->ra);
- mpgl_uninit(p->glctx);
-}
-
-static int preinit(struct vo *vo)
-{
- struct gl_priv *p = vo->priv;
- p->vo = vo;
- p->log = vo->log;
-
- int vo_flags = 0;
-
- int alpha_mode;
- mp_read_option_raw(vo->global, "alpha", &m_option_type_choice, &alpha_mode);
-
- if (alpha_mode == 1)
- vo_flags |= VOFLAG_ALPHA;
-
- if (p->opts.use_gl_debug)
- vo_flags |= VOFLAG_GL_DEBUG;
-
- if (p->opts.es == 1)
- vo_flags |= VOFLAG_GLES;
- if (p->opts.es == 2)
- vo_flags |= VOFLAG_GLES | VOFLAG_GLES2;
- if (p->opts.es == -1)
- vo_flags |= VOFLAG_NO_GLES;
-
- if (p->opts.allow_sw)
- vo_flags |= VOFLAG_SW;
-
- p->glctx = mpgl_init(vo, p->opts.backend, vo_flags);
- if (!p->glctx)
- goto err_out;
- p->gl = p->glctx->gl;
-
- if (p->gl->SwapInterval) {
- p->gl->SwapInterval(p->opts.swap_interval);
- } else {
- MP_VERBOSE(vo, "swap_control extension missing.\n");
- }
-
- p->ra = ra_create_gl(p->gl, vo->log);
- if (!p->ra)
- goto err_out;
-
- p->renderer = gl_video_init(p->ra, vo->log, vo->global);
- gl_video_set_osd_source(p->renderer, vo->osd);
- gl_video_configure_queue(p->renderer, vo);
-
- get_and_update_icc_profile(p);
-
- vo->hwdec_devs = hwdec_devices_create();
-
- hwdec_devices_set_loader(vo->hwdec_devs, call_request_hwdec_api, vo);
-
- p->hwdec = ra_hwdec_load(p->vo->log, p->ra, vo->global,
- vo->hwdec_devs, vo->opts->gl_hwdec_interop);
- gl_video_set_hwdec(p->renderer, p->hwdec);
-
- gl_check_error(p->gl, p->log, "before retrieving framebuffer depth");
- int fb_depth = gl_get_fb_depth(p->gl, p->glctx->main_fb);
- gl_check_error(p->gl, p->log, "retrieving framebuffer depth");
- if (fb_depth)
- MP_VERBOSE(p, "Reported display depth: %d\n", fb_depth);
- gl_video_set_fb_depth(p->renderer, fb_depth);
-
- return 0;
-
-err_out:
- uninit(vo);
- return -1;
-}
-
-#define OPT_BASE_STRUCT struct gl_priv
-
-const struct vo_driver video_out_opengl = {
- .description = "Extended OpenGL Renderer",
- .name = "opengl",
- .caps = VO_CAP_ROTATE90,
- .preinit = preinit,
- .query_format = query_format,
- .reconfig = reconfig,
- .control = control,
- .get_image = get_image,
- .draw_frame = draw_frame,
- .flip_page = flip_page,
- .wait_events = wait_events,
- .wakeup = wakeup,
- .uninit = uninit,
- .priv_size = sizeof(struct gl_priv),
- .options = (const m_option_t[]) {
- OPT_FLAG("opengl-glfinish", opts.use_glFinish, 0),
- OPT_FLAG("opengl-waitvsync", opts.waitvsync, 0),
- OPT_INT("opengl-swapinterval", opts.swap_interval, 0),
- OPT_FLAG("opengl-debug", opts.use_gl_debug, 0),
- OPT_STRING_VALIDATE("opengl-backend", opts.backend, 0,
- mpgl_validate_backend_opt),
- OPT_FLAG("opengl-sw", opts.allow_sw, 0),
- OPT_CHOICE("opengl-es", opts.es, 0, ({"no", -1}, {"auto", 0},
- {"yes", 1}, {"force2", 2})),
- OPT_INTPAIR("opengl-check-pattern", opts.pattern, 0),
- OPT_INTRANGE("opengl-vsync-fences", opts.vsync_fences, 0,
- 0, NUM_VSYNC_FENCES),
-
- {0}
- },
- .priv_defaults = &(const struct gl_priv){
- .opts = {
- .swap_interval = 1,
- },
- },
-};
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index ea6aaa9..c8dab15 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -24,9 +24,10 @@
#include "common/global.h"
#include "player/client.h"
+#include "gpu/video.h"
+#include "gpu/hwdec.h"
#include "opengl/common.h"
-#include "opengl/video.h"
-#include "opengl/hwdec.h"
+#include "opengl/context.h"
#include "opengl/ra_gl.h"
#include "libmpv/opengl_cb.h"
@@ -86,9 +87,8 @@ struct mpv_opengl_cb_context {
// application's OpenGL context is current - i.e. only while the
// host application is calling certain mpv_opengl_cb_* APIs.
GL *gl;
- struct ra *ra;
+ struct ra_ctx *ra_ctx;
struct gl_video *renderer;
- struct ra_hwdec *hwdec;
struct m_config_cache *vo_opts_cache;
struct mp_vo_opts *vo_opts;
};
@@ -171,18 +171,34 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts,
return MPV_ERROR_UNSUPPORTED;
}
- ctx->ra = ra_create_gl(ctx->gl, ctx->log);
- if (!ctx->ra)
- return MPV_ERROR_UNSUPPORTED;
+ // initialize a blank ra_ctx to reuse ra_gl_ctx
+ ctx->ra_ctx = talloc_zero(ctx, struct ra_ctx);
+ ctx->ra_ctx->log = ctx->log;
+ ctx->ra_ctx->global = ctx->global;
+ ctx->ra_ctx->opts = (struct ra_ctx_opts) {
+ .probing = false,
+ .allow_sw = true,
+ };
+
+ static const struct ra_swapchain_fns empty_swapchain_fns = {0};
+ struct ra_gl_ctx_params gl_params = {
+ // vo_opengl_cb is essentially like a gigantic external swapchain where
+ // the user is in charge of presentation / swapping etc. But we don't
+ // actually need to provide any of these functions, since we can just
+ // not call them to begin with - so just set it to an empty object to
+ // signal to ra_gl_ctx that we don't care about its latency emulation
+ // functionality
+ .external_swapchain = &empty_swapchain_fns
+ };
- ctx->renderer = gl_video_init(ctx->ra, ctx->log, ctx->global);
+ ctx->gl->SwapInterval = NULL; // we shouldn't randomly change this, so lock it
+ if (!ra_gl_ctx_init(ctx->ra_ctx, ctx->gl, gl_params))
+ return MPV_ERROR_UNSUPPORTED;
- m_config_cache_update(ctx->vo_opts_cache);
+ ctx->renderer = gl_video_init(ctx->ra_ctx->ra, ctx->log, ctx->global);
ctx->hwdec_devs = hwdec_devices_create();
- ctx->hwdec = ra_hwdec_load(ctx->log, ctx->ra, ctx->global,
- ctx->hwdec_devs, ctx->vo_opts->gl_hwdec_interop);
- gl_video_set_hwdec(ctx->renderer, ctx->hwdec);
+ gl_video_load_hwdecs(ctx->renderer, ctx->hwdec_devs, true);
pthread_mutex_lock(&ctx->lock);
for (int n = IMGFMT_START; n < IMGFMT_END; n++) {
@@ -217,12 +233,12 @@ int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx)
gl_video_uninit(ctx->renderer);
ctx->renderer = NULL;
- ra_hwdec_uninit(ctx->hwdec);
- ctx->hwdec = NULL;
hwdec_devices_destroy(ctx->hwdec_devs);
ctx->hwdec_devs = NULL;
- ra_free(&ctx->ra);
+ ra_gl_ctx_uninit(ctx->ra_ctx);
+ talloc_free(ctx->ra_ctx);
talloc_free(ctx->gl);
+ ctx->ra_ctx = NULL;
ctx->gl = NULL;
return 0;
}
@@ -236,11 +252,6 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
return MPV_ERROR_UNSUPPORTED;
}
- struct fbodst target = {
- .tex = ra_create_wrapped_fb(ctx->ra, fbo, vp_w, abs(vp_h)),
- .flip = vp_h < 0,
- };
-
reset_gl_state(ctx->gl);
pthread_mutex_lock(&ctx->lock);
@@ -273,14 +284,13 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
gl_video_config(ctx->renderer, &ctx->img_params);
}
if (ctx->update_new_opts) {
- gl_video_update_options(ctx->renderer);
if (vo)
gl_video_configure_queue(ctx->renderer, vo);
int debug;
- mp_read_option_raw(ctx->global, "opengl-debug", &m_option_type_flag,
+ mp_read_option_raw(ctx->global, "gpu-debug", &m_option_type_flag,
&debug);
ctx->gl->debug_context = debug;
- ra_gl_set_debug(ctx->ra, debug);
+ ra_gl_set_debug(ctx->ra_ctx->ra, debug);
if (gl_video_icc_auto_enabled(ctx->renderer))
MP_ERR(ctx, "icc-profile-auto is not available with opengl-cb\n");
}
@@ -316,7 +326,13 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
pthread_mutex_unlock(&ctx->lock);
MP_STATS(ctx, "glcb-render");
+ struct ra_swapchain *sw = ctx->ra_ctx->swapchain;
+ struct ra_fbo target;
+ ra_gl_ctx_resize(sw, vp_w, abs(vp_h), fbo);
+ ra_gl_ctx_start_frame(sw, &target);
+ target.flip = vp_h < 0;
gl_video_render_frame(ctx->renderer, frame, target);
+ ra_gl_ctx_submit_frame(sw, frame);
reset_gl_state(ctx->gl);
@@ -328,8 +344,6 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
pthread_cond_wait(&ctx->wakeup, &ctx->lock);
pthread_mutex_unlock(&ctx->lock);
- ra_tex_free(ctx->ra, &target.tex);
-
return 0;
}
diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c
index 5b5d62c..4322a3f 100644
--- a/video/out/vo_rpi.c
+++ b/video/out/vo_rpi.c
@@ -44,7 +44,7 @@
#include "sub/osd.h"
#include "opengl/ra_gl.h"
-#include "opengl/video.h"
+#include "gpu/video.h"
struct mp_egl_rpi {
struct mp_log *log;
@@ -261,7 +261,7 @@ static void update_osd(struct vo *vo)
MP_STATS(vo, "start rpi_osd");
struct vo_frame frame = {0};
- struct fbodst target = {
+ struct ra_fbo target = {
.tex = ra_create_wrapped_fb(p->egl.ra, 0, p->osd_res.w, p->osd_res.h),
.flip = true,
};
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
index 3468ac6..a3f7015 100644
--- a/video/out/vo_vaapi.c
+++ b/video/out/vo_vaapi.c
@@ -96,6 +96,8 @@ struct priv {
VADisplayAttribute *va_display_attrs;
int *mp_display_attr;
int va_num_display_attrs;
+
+ struct va_image_formats *image_formats;
};
#define OSD_VA_FORMAT VA_FOURCC_BGRA
@@ -108,6 +110,306 @@ static const bool osd_formats[SUBBITMAP_COUNT] = {
static void draw_osd(struct vo *vo);
+
+struct fmtentry {
+ uint32_t va;
+ enum mp_imgfmt mp;
+};
+
+static const struct fmtentry va_to_imgfmt[] = {
+ {VA_FOURCC_NV12, IMGFMT_NV12},
+ {VA_FOURCC_YV12, IMGFMT_420P},
+ {VA_FOURCC_IYUV, IMGFMT_420P},
+ {VA_FOURCC_UYVY, IMGFMT_UYVY},
+ // Note: not sure about endian issues (the mp formats are byte-addressed)
+ {VA_FOURCC_RGBA, IMGFMT_RGBA},
+ {VA_FOURCC_RGBX, IMGFMT_RGBA},
+ {VA_FOURCC_BGRA, IMGFMT_BGRA},
+ {VA_FOURCC_BGRX, IMGFMT_BGRA},
+ {0 , IMGFMT_NONE}
+};
+
+static enum mp_imgfmt va_fourcc_to_imgfmt(uint32_t fourcc)
+{
+ for (const struct fmtentry *entry = va_to_imgfmt; entry->va; ++entry) {
+ if (entry->va == fourcc)
+ return entry->mp;
+ }
+ return IMGFMT_NONE;
+}
+
+static uint32_t va_fourcc_from_imgfmt(int imgfmt)
+{
+ for (const struct fmtentry *entry = va_to_imgfmt; entry->va; ++entry) {
+ if (entry->mp == imgfmt)
+ return entry->va;
+ }
+ return 0;
+}
+
+struct va_image_formats {
+ VAImageFormat *entries;
+ int num;
+};
+
+static void va_get_formats(struct priv *ctx)
+{
+ struct va_image_formats *formats = talloc_ptrtype(ctx, formats);
+ formats->num = vaMaxNumImageFormats(ctx->display);
+ formats->entries = talloc_array(formats, VAImageFormat, formats->num);
+ VAStatus status = vaQueryImageFormats(ctx->display, formats->entries,
+ &formats->num);
+ if (!CHECK_VA_STATUS(ctx, "vaQueryImageFormats()"))
+ return;
+ MP_VERBOSE(ctx, "%d image formats available:\n", formats->num);
+ for (int i = 0; i < formats->num; i++)
+ MP_VERBOSE(ctx, " %s\n", mp_tag_str(formats->entries[i].fourcc));
+ ctx->image_formats = formats;
+}
+
+static VAImageFormat *va_image_format_from_imgfmt(struct priv *ctx,
+ int imgfmt)
+{
+ struct va_image_formats *formats = ctx->image_formats;
+ const int fourcc = va_fourcc_from_imgfmt(imgfmt);
+ if (!formats || !formats->num || !fourcc)
+ return NULL;
+ for (int i = 0; i < formats->num; i++) {
+ if (formats->entries[i].fourcc == fourcc)
+ return &formats->entries[i];
+ }
+ return NULL;
+}
+
+struct va_surface {
+ struct mp_vaapi_ctx *ctx;
+ VADisplay display;
+
+ VASurfaceID id;
+ int rt_format;
+
+ // The actually allocated surface size (needed for cropping).
+ // mp_images can have a smaller size than this, which means they are
+ // cropped down to a smaller size by removing right/bottom pixels.
+ int w, h;
+
+ VAImage image; // used for software decoding case
+ bool is_derived; // is image derived by vaDeriveImage()?
+};
+
+static struct va_surface *va_surface_in_mp_image(struct mp_image *mpi)
+{
+ return mpi && mpi->imgfmt == IMGFMT_VAAPI ?
+ (struct va_surface*)mpi->planes[0] : NULL;
+}
+
+static void release_va_surface(void *arg)
+{
+ struct va_surface *surface = arg;
+
+ if (surface->id != VA_INVALID_ID) {
+ if (surface->image.image_id != VA_INVALID_ID)
+ vaDestroyImage(surface->display, surface->image.image_id);
+ vaDestroySurfaces(surface->display, &surface->id, 1);
+ }
+
+ talloc_free(surface);
+}
+
+static struct mp_image *alloc_surface(struct mp_vaapi_ctx *ctx, int rt_format,
+ int w, int h)
+{
+ VASurfaceID id = VA_INVALID_ID;
+ VAStatus status;
+ status = vaCreateSurfaces(ctx->display, rt_format, w, h, &id, 1, NULL, 0);
+ if (!CHECK_VA_STATUS(ctx, "vaCreateSurfaces()"))
+ return NULL;
+
+ struct va_surface *surface = talloc_ptrtype(NULL, surface);
+ if (!surface)
+ return NULL;
+
+ *surface = (struct va_surface){
+ .ctx = ctx,
+ .id = id,
+ .rt_format = rt_format,
+ .w = w,
+ .h = h,
+ .display = ctx->display,
+ .image = { .image_id = VA_INVALID_ID, .buf = VA_INVALID_ID },
+ };
+
+ struct mp_image img = {0};
+ mp_image_setfmt(&img, IMGFMT_VAAPI);
+ mp_image_set_size(&img, w, h);
+ img.planes[0] = (uint8_t*)surface;
+ img.planes[3] = (uint8_t*)(uintptr_t)surface->id;
+ return mp_image_new_custom_ref(&img, surface, release_va_surface);
+}
+
+static void va_surface_image_destroy(struct va_surface *surface)
+{
+ if (!surface || surface->image.image_id == VA_INVALID_ID)
+ return;
+ vaDestroyImage(surface->display, surface->image.image_id);
+ surface->image.image_id = VA_INVALID_ID;
+ surface->is_derived = false;
+}
+
+static int va_surface_image_alloc(struct va_surface *p, VAImageFormat *format)
+{
+ VADisplay *display = p->display;
+
+ if (p->image.image_id != VA_INVALID_ID &&
+ p->image.format.fourcc == format->fourcc)
+ return 0;
+
+ int r = 0;
+
+ va_surface_image_destroy(p);
+
+ VAStatus status = vaDeriveImage(display, p->id, &p->image);
+ if (status == VA_STATUS_SUCCESS) {
+ /* vaDeriveImage() is supported, check format */
+ if (p->image.format.fourcc == format->fourcc &&
+ p->image.width == p->w && p->image.height == p->h)
+ {
+ p->is_derived = true;
+ MP_TRACE(p->ctx, "Using vaDeriveImage()\n");
+ } else {
+ vaDestroyImage(p->display, p->image.image_id);
+ status = VA_STATUS_ERROR_OPERATION_FAILED;
+ }
+ }
+ if (status != VA_STATUS_SUCCESS) {
+ p->image.image_id = VA_INVALID_ID;
+ status = vaCreateImage(p->display, format, p->w, p->h, &p->image);
+ if (!CHECK_VA_STATUS(p->ctx, "vaCreateImage()")) {
+ p->image.image_id = VA_INVALID_ID;
+ r = -1;
+ }
+ }
+
+ return r;
+}
+
+// img must be a VAAPI surface; make sure its internal VAImage is allocated
+// to a format corresponding to imgfmt (or return an error).
+static int va_surface_alloc_imgfmt(struct priv *priv, struct mp_image *img,
+ int imgfmt)
+{
+ struct va_surface *p = va_surface_in_mp_image(img);
+ if (!p)
+ return -1;
+ // Multiple FourCCs can refer to the same imgfmt, so check by doing the
+ // surjective conversion first.
+ if (p->image.image_id != VA_INVALID_ID &&
+ va_fourcc_to_imgfmt(p->image.format.fourcc) == imgfmt)
+ return 0;
+ VAImageFormat *format = va_image_format_from_imgfmt(priv, imgfmt);
+ if (!format)
+ return -1;
+ if (va_surface_image_alloc(p, format) < 0)
+ return -1;
+ return 0;
+}
+
+static bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image,
+ struct mp_image *mpi)
+{
+ int imgfmt = va_fourcc_to_imgfmt(image->format.fourcc);
+ if (imgfmt == IMGFMT_NONE)
+ return false;
+ void *data = NULL;
+ const VAStatus status = vaMapBuffer(ctx->display, image->buf, &data);
+ if (!CHECK_VA_STATUS(ctx, "vaMapBuffer()"))
+ return false;
+
+ *mpi = (struct mp_image) {0};
+ mp_image_setfmt(mpi, imgfmt);
+ mp_image_set_size(mpi, image->width, image->height);
+
+ for (int p = 0; p < image->num_planes; p++) {
+ mpi->stride[p] = image->pitches[p];
+ mpi->planes[p] = (uint8_t *)data + image->offsets[p];
+ }
+
+ if (image->format.fourcc == VA_FOURCC_YV12) {
+ MPSWAP(int, mpi->stride[1], mpi->stride[2]);
+ MPSWAP(uint8_t *, mpi->planes[1], mpi->planes[2]);
+ }
+
+ return true;
+}
+
+static bool va_image_unmap(struct mp_vaapi_ctx *ctx, VAImage *image)
+{
+ const VAStatus status = vaUnmapBuffer(ctx->display, image->buf);
+ return CHECK_VA_STATUS(ctx, "vaUnmapBuffer()");
+}
+
+// va_dst: copy destination, must be IMGFMT_VAAPI
+// sw_src: copy source, must be a software pixel format
+static int va_surface_upload(struct priv *priv, struct mp_image *va_dst,
+ struct mp_image *sw_src)
+{
+ struct va_surface *p = va_surface_in_mp_image(va_dst);
+ if (!p)
+ return -1;
+
+ if (va_surface_alloc_imgfmt(priv, va_dst, sw_src->imgfmt) < 0)
+ return -1;
+
+ struct mp_image img;
+ if (!va_image_map(p->ctx, &p->image, &img))
+ return -1;
+ assert(sw_src->w <= img.w && sw_src->h <= img.h);
+ mp_image_set_size(&img, sw_src->w, sw_src->h); // copy only visible part
+ mp_image_copy(&img, sw_src);
+ va_image_unmap(p->ctx, &p->image);
+
+ if (!p->is_derived) {
+ VAStatus status = vaPutImage(p->display, p->id,
+ p->image.image_id,
+ 0, 0, sw_src->w, sw_src->h,
+ 0, 0, sw_src->w, sw_src->h);
+ if (!CHECK_VA_STATUS(p->ctx, "vaPutImage()"))
+ return -1;
+ }
+
+ if (p->is_derived)
+ va_surface_image_destroy(p);
+ return 0;
+}
+
+struct pool_alloc_ctx {
+ struct mp_vaapi_ctx *vaapi;
+ int rt_format;
+};
+
+static struct mp_image *alloc_pool(void *pctx, int fmt, int w, int h)
+{
+ struct pool_alloc_ctx *alloc_ctx = pctx;
+ if (fmt != IMGFMT_VAAPI)
+ return NULL;
+
+ return alloc_surface(alloc_ctx->vaapi, alloc_ctx->rt_format, w, h);
+}
+
+// The allocator of the given image pool to allocate VAAPI surfaces, using
+// the given rt_format.
+static void va_pool_set_allocator(struct mp_image_pool *pool,
+ struct mp_vaapi_ctx *ctx, int rt_format)
+{
+ struct pool_alloc_ctx *alloc_ctx = talloc_ptrtype(pool, alloc_ctx);
+ *alloc_ctx = (struct pool_alloc_ctx){
+ .vaapi = ctx,
+ .rt_format = rt_format,
+ };
+ mp_image_pool_set_allocator(pool, alloc_pool, alloc_ctx);
+ mp_image_pool_set_lru(pool);
+}
+
static void flush_output_surfaces(struct priv *p)
{
for (int n = 0; n < MAX_OUTPUT_SURFACES; n++)
@@ -135,7 +437,7 @@ static bool alloc_swdec_surfaces(struct priv *p, int w, int h, int imgfmt)
free_video_specific(p);
for (int i = 0; i < MAX_OUTPUT_SURFACES; i++) {
p->swdec_surfaces[i] = mp_image_pool_get(p->pool, IMGFMT_VAAPI, w, h);
- if (va_surface_alloc_imgfmt(p->swdec_surfaces[i], imgfmt) < 0)
+ if (va_surface_alloc_imgfmt(p, p->swdec_surfaces[i], imgfmt) < 0)
return false;
}
return true;
@@ -172,7 +474,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
static int query_format(struct vo *vo, int imgfmt)
{
struct priv *p = vo->priv;
- if (imgfmt == IMGFMT_VAAPI || va_image_format_from_imgfmt(p->mpvaapi, imgfmt))
+ if (imgfmt == IMGFMT_VAAPI || va_image_format_from_imgfmt(p, imgfmt))
return 1;
return 0;
@@ -193,7 +495,7 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
struct mp_image *img = mp_image_alloc(fmt, w, h);
if (img) {
mp_image_clear(img, 0, 0, w, h);
- if (va_surface_upload(p->black_surface, img) < 0)
+ if (va_surface_upload(p, p->black_surface, img) < 0)
mp_image_unrefp(&p->black_surface);
talloc_free(img);
}
@@ -268,7 +570,7 @@ static void draw_image(struct vo *vo, struct mp_image *mpi)
if (mpi->imgfmt != IMGFMT_VAAPI) {
struct mp_image *dst = p->swdec_surfaces[p->output_surface];
- if (!dst || va_surface_upload(dst, mpi) < 0) {
+ if (!dst || va_surface_upload(p, dst, mpi) < 0) {
MP_WARN(vo, "Could not upload surface.\n");
talloc_free(mpi);
return;
@@ -510,6 +812,10 @@ static int preinit(struct vo *vo)
"It's better to use VDPAU directly with: --vo=vdpau\n");
}
+ va_get_formats(p);
+ if (!p->image_formats)
+ goto fail;
+
p->pool = mp_image_pool_new(MAX_OUTPUT_SURFACES + 3);
va_pool_set_allocator(p->pool, p->mpvaapi, VA_RT_FORMAT_YUV420);
diff --git a/video/out/vo_wayland.c b/video/out/vo_wayland.c
deleted file mode 100644
index 37ab4c7..0000000
--- a/video/out/vo_wayland.c
+++ /dev/null
@@ -1,682 +0,0 @@
-/*
- * This file is part of mpv video player.
- * Copyright © 2013 Alexander Preisinger <alexander.preisinger@gmail.com>
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdbool.h>
-#include <assert.h>
-
-#include <libavutil/common.h>
-
-#include "config.h"
-
-#include "vo.h"
-#include "video/mp_image.h"
-#include "video/sws_utils.h"
-#include "sub/osd.h"
-#include "sub/img_convert.h"
-#include "common/msg.h"
-#include "input/input.h"
-#include "osdep/endian.h"
-#include "osdep/timer.h"
-
-#include "wayland_common.h"
-
-#include "video/out/wayland/buffer.h"
-
-static void draw_image(struct vo *vo, mp_image_t *mpi);
-static void draw_osd(struct vo *vo);
-
-static const struct wl_buffer_listener buffer_listener;
-
-// TODO: pay attention to the reported subpixel order
-static const format_t format_table[] = {
- {WL_SHM_FORMAT_ARGB8888, IMGFMT_BGRA}, // 8b 8g 8r 8a
- {WL_SHM_FORMAT_XRGB8888, IMGFMT_BGR0},
-#if BYTE_ORDER == LITTLE_ENDIAN
- {WL_SHM_FORMAT_RGB565, IMGFMT_RGB565}, // 5b 6g 5r
-#endif
- {WL_SHM_FORMAT_RGB888, IMGFMT_BGR24}, // 8b 8g 8r
- {WL_SHM_FORMAT_BGR888, IMGFMT_RGB24}, // 8r 8g 8b
- {WL_SHM_FORMAT_XBGR8888, IMGFMT_RGB0},
- {WL_SHM_FORMAT_RGBX8888, IMGFMT_0BGR},
- {WL_SHM_FORMAT_BGRX8888, IMGFMT_0RGB},
- {WL_SHM_FORMAT_ABGR8888, IMGFMT_RGBA},
- {WL_SHM_FORMAT_RGBA8888, IMGFMT_ABGR},
- {WL_SHM_FORMAT_BGRA8888, IMGFMT_ARGB},
-};
-
-#define MAX_FORMAT_ENTRIES (sizeof(format_table) / sizeof(format_table[0]))
-#define DEFAULT_FORMAT_ENTRY 1
-#define DEFAULT_ALPHA_FORMAT_ENTRY 0
-
-struct priv;
-
-// We only use double buffering but the creation and usage is still open to
-// triple buffering. Triple buffering is now removed, because double buffering
-// is now pixel-perfect.
-struct buffer_pool {
- shm_buffer_t **buffers;
- shm_buffer_t *front_buffer; // just pointers to any of the buffers
- shm_buffer_t *back_buffer;
- uint32_t buffer_no;
-};
-
-struct supported_format {
- format_t format;
- bool is_alpha;
- struct wl_list link;
-};
-
-struct priv {
- struct vo *vo;
- struct vo_wayland_state *wl;
-
- struct wl_list format_list;
- const format_t *video_format; // pointer to element in supported_format list
-
- struct mp_rect src;
- struct mp_rect dst;
- int src_w, src_h;
- int dst_w, dst_h;
- struct mp_osd_res osd;
-
- struct mp_sws_context *sws;
- struct mp_image_params in_format;
-
- struct buffer_pool video_bufpool;
-
- struct mp_image *original_image;
- int width; // width of the original image
- int height;
-
- int x, y; // coords for resizing
-
- struct wl_surface *osd_surfaces[MAX_OSD_PARTS];
- struct wl_subsurface *osd_subsurfaces[MAX_OSD_PARTS];
- shm_buffer_t *osd_buffers[MAX_OSD_PARTS];
- // this id tells us if the subtitle part has changed or not
- int change_id[MAX_OSD_PARTS];
-
- // options
- int enable_alpha;
- int use_rgb565;
-};
-
-static bool is_alpha_format(const format_t *fmt)
-{
- return !!(mp_imgfmt_get_desc(fmt->mp_format).flags & MP_IMGFLAG_ALPHA);
-}
-
-static const format_t* is_wayland_format_supported(struct priv *p,
- enum wl_shm_format fmt)
-{
- struct supported_format *sf;
-
- // find the matching format first
- wl_list_for_each(sf, &p->format_list, link) {
- if (sf->format.wl_format == fmt) {
- return &sf->format;
- }
- }
-
- return NULL;
-}
-
-// additional buffer functions
-
-static void buffer_finalise_front(shm_buffer_t *buf)
-{
- SHM_BUFFER_SET_BUSY(buf);
- SHM_BUFFER_CLEAR_DIRTY(buf);
-}
-
-static void buffer_finalise_back(shm_buffer_t *buf)
-{
- SHM_BUFFER_SET_DIRTY(buf);
-}
-
-static struct mp_image buffer_get_mp_image(struct priv *p,
- shm_buffer_t *buf)
-{
- struct mp_image img = {0};
- mp_image_set_params(&img, &p->sws->dst);
-
- img.w = buf->stride / buf->bytes;
- img.h = buf->height;
- img.planes[0] = buf->data;
- img.stride[0] = buf->stride;
-
- return img;
-}
-
-// buffer pool functions
-
-static void buffer_pool_reinit(struct priv *p,
- struct buffer_pool *pool,
- uint32_t buffer_no,
- uint32_t width, uint32_t height,
- format_t fmt,
- struct wl_shm *shm)
-{
- if (!pool->buffers)
- pool->buffers = calloc(buffer_no, sizeof(shm_buffer_t*));
-
- pool->buffer_no = buffer_no;
-
- for (uint32_t i = 0; i < buffer_no; ++i) {
- if (pool->buffers[i] == NULL)
- pool->buffers[i] = shm_buffer_create(width, height, fmt,
- shm, &buffer_listener);
- else
- shm_buffer_resize(pool->buffers[i], width, height);
- }
-
- pool->back_buffer = pool->buffers[0];
- pool->front_buffer = pool->buffers[1];
-}
-
-static bool buffer_pool_resize(struct buffer_pool *pool,
- int width,
- int height)
-{
- bool ret = true;
-
- for (uint32_t i = 0; ret && i < pool->buffer_no; ++i)
- shm_buffer_resize(pool->buffers[i], width, height);
-
- return ret;
-}
-
-static void buffer_pool_destroy(struct buffer_pool *pool)
-{
- for (uint32_t i = 0; i < pool->buffer_no; ++i)
- shm_buffer_destroy(pool->buffers[i]);
-
- free(pool->buffers);
- pool->front_buffer = NULL;
- pool->back_buffer = NULL;
- pool->buffers = NULL;
-}
-
-static void buffer_pool_swap(struct buffer_pool *pool)
-{
- if (SHM_BUFFER_IS_DIRTY(pool->back_buffer)) {
- shm_buffer_t *tmp = pool->back_buffer;
- pool->back_buffer = pool->front_buffer;
- pool->front_buffer = tmp;
- }
-}
-
-// returns NULL if the back buffer is busy
-static shm_buffer_t * buffer_pool_get_back(struct buffer_pool *pool)
-{
- if (!pool->back_buffer || SHM_BUFFER_IS_BUSY(pool->back_buffer))
- return NULL;
-
- return pool->back_buffer;
-}
-
-static shm_buffer_t * buffer_pool_get_front(struct buffer_pool *pool)
-{
- return pool->front_buffer;
-}
-
-static bool redraw_frame(struct priv *p)
-{
- draw_image(p->vo, NULL);
- return true;
-}
-
-static bool resize(struct priv *p)
-{
- struct vo_wayland_state *wl = p->wl;
-
- if (!p->video_bufpool.back_buffer || SHM_BUFFER_IS_BUSY(p->video_bufpool.back_buffer))
- return false; // skip resizing if we can't guarantee pixel perfectness!
-
- int32_t scale = 1;
- int32_t x = wl->window.sh_x;
- int32_t y = wl->window.sh_y;
-
- if (wl->display.current_output)
- scale = wl->display.current_output->scale;
-
- wl->vo->dwidth = scale*wl->window.sh_width;
- wl->vo->dheight = scale*wl->window.sh_height;
-
- vo_get_src_dst_rects(p->vo, &p->src, &p->dst, &p->osd);
- p->src_w = p->src.x1 - p->src.x0;
- p->src_h = p->src.y1 - p->src.y0;
- p->dst_w = p->dst.x1 - p->dst.x0;
- p->dst_h = p->dst.y1 - p->dst.y0;
-
- mp_input_set_mouse_transform(p->vo->input_ctx, &p->dst, NULL);
-
- MP_DBG(wl, "resizing %dx%d -> %dx%d\n", wl->window.width,
- wl->window.height,
- p->dst_w,
- p->dst_h);
-
- if (x != 0)
- x = wl->window.width - p->dst_w;
-
- if (y != 0)
- y = wl->window.height - p->dst_h;
-
- wl_surface_set_buffer_scale(wl->window.video_surface, scale);
- mp_sws_set_from_cmdline(p->sws, p->vo->opts->sws_opts);
- p->sws->src = p->in_format;
- p->sws->dst = (struct mp_image_params) {
- .imgfmt = p->video_format->mp_format,
- .w = p->dst_w,
- .h = p->dst_h,
- .p_w = 1,
- .p_h = 1,
- };
-
- mp_image_params_guess_csp(&p->sws->dst);
-
- if (mp_sws_reinit(p->sws) < 0)
- return false;
-
- if (!buffer_pool_resize(&p->video_bufpool, p->dst_w, p->dst_h)) {
- MP_ERR(wl, "failed to resize video buffers\n");
- return false;
- }
-
- wl->window.width = p->dst_w;
- wl->window.height = p->dst_h;
-
- // if no alpha enabled format is used then create an opaque region to allow
- // the compositor to optimize the drawing of the window
- if (!p->enable_alpha) {
- struct wl_region *opaque =
- wl_compositor_create_region(wl->display.compositor);
- wl_region_add(opaque, 0, 0, p->dst_w/scale, p->dst_h/scale);
- wl_surface_set_opaque_region(wl->window.video_surface, opaque);
- wl_region_destroy(opaque);
- }
-
- p->x = x;
- p->y = y;
- p->vo->want_redraw = true;
- return true;
-}
-
-
-/* wayland listeners */
-
-static void buffer_handle_release(void *data, struct wl_buffer *buffer)
-{
- shm_buffer_t *buf = data;
-
- if (SHM_BUFFER_IS_ONESHOT(buf)) {
- shm_buffer_destroy(buf);
- return;
- }
-
- SHM_BUFFER_CLEAR_BUSY(buf);
- // does nothing and returns 0 if no pending resize flag was set
- shm_buffer_pending_resize(buf);
-}
-
-static const struct wl_buffer_listener buffer_listener = {
- buffer_handle_release
-};
-
-static void shm_handle_format(void *data,
- struct wl_shm *wl_shm,
- uint32_t format)
-{
- struct priv *p = data;
- for (uint32_t i = 0; i < MAX_FORMAT_ENTRIES; ++i) {
- if (format_table[i].wl_format == format) {
- MP_INFO(p->wl, "format %s supported by hw\n",
- mp_imgfmt_to_name(format_table[i].mp_format));
- struct supported_format *sf = talloc(p, struct supported_format);
- sf->format = format_table[i];
- sf->is_alpha = is_alpha_format(&sf->format);
- wl_list_insert(&p->format_list, &sf->link);
- }
- }
-}
-
-static const struct wl_shm_listener shm_listener = {
- shm_handle_format
-};
-
-
-/* mpv interface */
-
-static void draw_image(struct vo *vo, mp_image_t *mpi)
-{
- struct priv *p = vo->priv;
-
- if (mpi) {
- talloc_free(p->original_image);
- p->original_image = mpi;
- }
-
- vo_wayland_wait_events(vo, 0);
-
- shm_buffer_t *buf = buffer_pool_get_back(&p->video_bufpool);
-
- if (!buf) {
- MP_VERBOSE(p->wl, "can't draw, back buffer is busy\n");
- return;
- }
-
- struct mp_image img = buffer_get_mp_image(p, buf);
-
- if (p->original_image) {
- struct mp_image src = *p->original_image;
- struct mp_rect src_rc = p->src;
- src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, src.fmt.align_x);
- src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, src.fmt.align_y);
- mp_image_crop_rc(&src, src_rc);
-
- mp_sws_scale(p->sws, &img, &src);
- } else {
- mp_image_clear(&img, 0, 0, img.w, img.h);
- }
-
- buffer_finalise_back(buf);
-
- draw_osd(vo);
-}
-
-static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
-{
- struct priv *p = ctx;
- int id = imgs->render_index;
-
- struct wl_surface *s = p->osd_surfaces[id];
-
- if (imgs->change_id != p->change_id[id]) {
- p->change_id[id] = imgs->change_id;
-
- struct mp_rect bb;
- if (!mp_sub_bitmaps_bb(imgs, &bb))
- return;
-
- int width = mp_rect_w(bb);
- int height = mp_rect_h(bb);
-
- if (!p->osd_buffers[id]) {
- p->osd_buffers[id] = shm_buffer_create(width,
- height,
- format_table[DEFAULT_ALPHA_FORMAT_ENTRY],
- p->wl->display.shm,
- &buffer_listener);
- }
- else if (SHM_BUFFER_IS_BUSY(p->osd_buffers[id])) {
- // freed on release in buffer_listener
- // guarantees pixel perfect resizing of subtitles and osd
- SHM_BUFFER_SET_ONESHOT(p->osd_buffers[id]);
- p->osd_buffers[id] = shm_buffer_create(width,
- height,
- format_table[DEFAULT_ALPHA_FORMAT_ENTRY],
- p->wl->display.shm,
- &buffer_listener);
- }
- else {
- shm_buffer_resize(p->osd_buffers[id], width, height);
- }
-
- shm_buffer_t *buf = p->osd_buffers[id];
- SHM_BUFFER_SET_BUSY(buf);
-
- struct mp_image wlimg = buffer_get_mp_image(p, buf);
-
- for (int n = 0; n < imgs->num_parts; n++) {
- struct sub_bitmap *sub = &imgs->parts[n];
- memcpy_pic(wlimg.planes[0], sub->bitmap, sub->w * 4, sub->h,
- wlimg.stride[0], sub->stride);
- }
-
- wl_subsurface_set_position(p->osd_subsurfaces[id], 0, 0);
- wl_surface_attach(s, buf->buffer, bb.x0, bb.y0);
- wl_surface_damage(s, 0, 0, width, height);
- wl_surface_commit(s);
- }
- else {
- // p->osd_buffer, guaranteed to exist here
- assert(p->osd_buffers[id]);
- wl_surface_attach(s, p->osd_buffers[id]->buffer, 0, 0);
- wl_surface_commit(s);
- }
-}
-
-static const bool osd_formats[SUBBITMAP_COUNT] = {
- [SUBBITMAP_RGBA] = true,
-};
-
-static void draw_osd(struct vo *vo)
-{
- int32_t scale = 1;
- struct priv *p = vo->priv;
-
- if (p->wl && p->wl->display.current_output)
- scale = p->wl->display.current_output->scale;
-
- // detach all buffers and attach all needed buffers in osd_draw
- // only the most recent attach & commit is applied once the parent surface
- // is committed
- for (int i = 0; i < MAX_OSD_PARTS; ++i) {
- struct wl_surface *s = p->osd_surfaces[i];
- wl_surface_attach(s, NULL, 0, 0);
- wl_surface_set_buffer_scale(s, scale);
- wl_surface_damage(s, 0, 0, p->dst_w, p->dst_h);
- wl_surface_commit(s);
- }
-
- double pts = p->original_image ? p->original_image->pts : 0;
- osd_draw(vo->osd, p->osd, pts, 0, osd_formats, draw_osd_cb, p);
-}
-
-static void redraw(void *data, uint32_t time)
-{
- struct priv *p = data;
-
- shm_buffer_t *buf = buffer_pool_get_front(&p->video_bufpool);
- wl_surface_attach(p->wl->window.video_surface, buf->buffer, p->x, p->y);
- wl_surface_damage(p->wl->window.video_surface, 0, 0, p->dst_w, p->dst_h);
- buffer_finalise_front(buf);
-
- p->x = 0;
- p->y = 0;
-}
-
-static void flip_page(struct vo *vo)
-{
- struct priv *p = vo->priv;
-
- buffer_pool_swap(&p->video_bufpool);
-
- if (!p->wl->frame.callback)
- vo_wayland_request_frame(vo, p, redraw);
-
- vo_wayland_wait_events(vo, 0);
-}
-
-static int query_format(struct vo *vo, int format)
-{
- struct priv *p = vo->priv;
- struct supported_format *sf;
- wl_list_for_each_reverse(sf, &p->format_list, link) {
- if (sf->format.mp_format == format)
- return 1;
- }
-
- if (mp_sws_supported_format(format))
- return 1;
-
- return 0;
-}
-
-static int reconfig(struct vo *vo, struct mp_image_params *fmt)
-{
- struct priv *p = vo->priv;
- mp_image_unrefp(&p->original_image);
-
- p->width = fmt->w;
- p->height = fmt->h;
- p->in_format = *fmt;
-
- struct supported_format *sf;
-
- // find the matching format first
- wl_list_for_each(sf, &p->format_list, link) {
- if (sf->format.mp_format == fmt->imgfmt &&
- (p->enable_alpha == sf->is_alpha))
- {
- p->video_format = &sf->format;
- break;
- }
- }
-
- if (!p->video_format) {
- // if use default is enable overwrite the auto selected one
- if (p->enable_alpha)
- p->video_format = &format_table[DEFAULT_ALPHA_FORMAT_ENTRY];
- else
- p->video_format = &format_table[DEFAULT_FORMAT_ENTRY];
- }
-
- // overrides alpha
- // use rgb565 if performance is your main concern
- if (p->use_rgb565) {
- MP_INFO(p->wl, "using rgb565\n");
- const format_t *entry =
- is_wayland_format_supported(p, WL_SHM_FORMAT_RGB565);
- if (entry)
- p->video_format = entry;
- }
-
- buffer_pool_reinit(p, &p->video_bufpool, 2, p->width, p->height,
- *p->video_format, p->wl->display.shm);
-
- vo_wayland_config(vo);
-
- resize(p);
-
- return 0;
-}
-
-static void uninit(struct vo *vo)
-{
- struct priv *p = vo->priv;
- buffer_pool_destroy(&p->video_bufpool);
-
- talloc_free(p->original_image);
-
- for (int i = 0; i < MAX_OSD_PARTS; ++i) {
- shm_buffer_destroy(p->osd_buffers[i]);
- wl_subsurface_destroy(p->osd_subsurfaces[i]);
- wl_surface_destroy(p->osd_surfaces[i]);
- }
-
- vo_wayland_uninit(vo);
-}
-
-static int preinit(struct vo *vo)
-{
- struct priv *p = vo->priv;
- struct vo_wayland_state *wl = NULL;
-
- if (!vo_wayland_init(vo))
- return -1;
-
- wl = vo->wayland;
-
- p->vo = vo;
- p->wl = wl;
- p->sws = mp_sws_alloc(vo);
-
- wl_list_init(&p->format_list);
-
- wl_shm_add_listener(wl->display.shm, &shm_listener, p);
- wl_display_dispatch(wl->display.display);
-
- // Commits on surfaces bound to a subsurface are cached until the parent
- // surface is committed, in this case the video surface.
- // Which means we can call commit anywhere.
- struct wl_region *input =
- wl_compositor_create_region(wl->display.compositor);
- for (int i = 0; i < MAX_OSD_PARTS; ++i) {
- p->osd_surfaces[i] =
- wl_compositor_create_surface(wl->display.compositor);
- wl_surface_attach(p->osd_surfaces[i], NULL, 0, 0);
- wl_surface_set_input_region(p->osd_surfaces[i], input);
- p->osd_subsurfaces[i] =
- wl_subcompositor_get_subsurface(wl->display.subcomp,
- p->osd_surfaces[i],
- wl->window.video_surface); // parent
- wl_surface_commit(p->osd_surfaces[i]);
- wl_subsurface_set_sync(p->osd_subsurfaces[i]);
- }
- wl_region_destroy(input);
-
- return 0;
-}
-
-static int control(struct vo *vo, uint32_t request, void *data)
-{
- struct priv *p = vo->priv;
- switch (request) {
- case VOCTRL_SET_PANSCAN: {
- resize(p);
- return VO_TRUE;
- }
- case VOCTRL_REDRAW_FRAME:
- return redraw_frame(p);
- }
- int events = 0;
- int r = vo_wayland_control(vo, &events, request, data);
-
- // NOTE: VO_EVENT_EXPOSE is never returned by the wayland backend
- if (events & VO_EVENT_RESIZE)
- resize(p);
-
- vo_event(vo, events);
-
- return r;
-}
-
-#define OPT_BASE_STRUCT struct priv
-const struct vo_driver video_out_wayland = {
- .description = "Wayland SHM video output",
- .name = "wayland",
- .priv_size = sizeof(struct priv),
- .preinit = preinit,
- .query_format = query_format,
- .reconfig = reconfig,
- .control = control,
- .draw_image = draw_image,
- .flip_page = flip_page,
- .wakeup = vo_wayland_wakeup,
- .wait_events = vo_wayland_wait_events,
- .uninit = uninit,
- .options = (const struct m_option[]) {
- OPT_FLAG("alpha", enable_alpha, 0),
- OPT_FLAG("rgb565", use_rgb565, 0),
- {0}
- },
- .options_prefix = "vo-wayland",
-};
-
diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c
index dd2d942..f29d06a 100644
--- a/video/out/vo_x11.c
+++ b/video/out/vo_x11.c
@@ -37,11 +37,9 @@
#include "x11_common.h"
-#if HAVE_SHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
-#endif
#include "sub/osd.h"
#include "sub/draw_bmp.h"
@@ -79,11 +77,9 @@ struct priv {
int current_buf;
bool reset_view;
-#if HAVE_SHM
int Shmem_Flag;
XShmSegmentInfo Shminfo[2];
int Shm_Warned_Slow;
-#endif
};
static bool resize(struct vo *vo);
@@ -91,7 +87,6 @@ static bool resize(struct vo *vo);
static bool getMyXImage(struct priv *p, int foo)
{
struct vo *vo = p->vo;
-#if HAVE_SHM
if (vo->x11->display_is_local && XShmQueryExtension(vo->x11->display)) {
p->Shmem_Flag = 1;
vo->x11->ShmCompletionEvent = XShmGetEventBase(vo->x11->display)
@@ -136,34 +131,29 @@ static bool getMyXImage(struct priv *p, int foo)
} else {
shmemerror:
p->Shmem_Flag = 0;
-#endif
- MP_VERBOSE(vo, "Not using SHM.\n");
- p->myximage[foo] =
- XCreateImage(vo->x11->display, p->vinfo.visual, p->depth, ZPixmap,
- 0, NULL, p->image_width, p->image_height, 8, 0);
- if (!p->myximage[foo]) {
- MP_WARN(vo, "could not allocate image");
- return false;
+
+ MP_VERBOSE(vo, "Not using SHM.\n");
+ p->myximage[foo] =
+ XCreateImage(vo->x11->display, p->vinfo.visual, p->depth, ZPixmap,
+ 0, NULL, p->image_width, p->image_height, 8, 0);
+ if (!p->myximage[foo]) {
+ MP_WARN(vo, "could not allocate image");
+ return false;
+ }
+ p->myximage[foo]->data =
+ calloc(1, p->myximage[foo]->bytes_per_line * p->image_height + 32);
}
- p->myximage[foo]->data =
- calloc(1, p->myximage[foo]->bytes_per_line * p->image_height + 32);
-#if HAVE_SHM
-}
-#endif
return true;
}
static void freeMyXImage(struct priv *p, int foo)
{
-#if HAVE_SHM
struct vo *vo = p->vo;
if (p->Shmem_Flag) {
XShmDetach(vo->x11->display, &p->Shminfo[foo]);
XDestroyImage(p->myximage[foo]);
shmdt(p->Shminfo[foo].shmaddr);
- } else
-#endif
- {
+ } else {
if (p->myximage[foo])
XDestroyImage(p->myximage[foo]);
}
@@ -284,15 +274,12 @@ static void Display_Image(struct priv *p, XImage *myximage)
p->reset_view = false;
}
-#if HAVE_SHM
if (p->Shmem_Flag) {
XShmPutImage(vo->x11->display, vo->x11->window, p->gc, x_image,
0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h,
True);
vo->x11->ShmCompletionWaitCount++;
- } else
-#endif
- {
+ } else {
XPutImage(vo->x11->display, vo->x11->window, p->gc, x_image,
0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h);
}
@@ -312,7 +299,6 @@ static struct mp_image get_x_buffer(struct priv *p, int buf_index)
static void wait_for_completion(struct vo *vo, int max_outstanding)
{
-#if HAVE_SHM
struct priv *ctx = vo->priv;
struct vo_x11_state *x11 = vo->x11;
if (ctx->Shmem_Flag) {
@@ -326,7 +312,6 @@ static void wait_for_completion(struct vo *vo, int max_outstanding)
vo_x11_check_events(vo);
}
}
-#endif
}
static void flip_page(struct vo *vo)
diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c
index 7c710f2..e75a653 100644
--- a/video/out/vo_xv.c
+++ b/video/out/vo_xv.c
@@ -30,12 +30,10 @@
#include "config.h"
-#if HAVE_SHM
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
-#endif
// Note: depends on the inclusion of X11/extensions/XShm.h
#include <X11/extensions/Xv.h>
@@ -92,10 +90,8 @@ struct xvctx {
GC f_gc; // used to paint background
GC vo_gc; // used to paint video
int Shmem_Flag;
-#if HAVE_SHM
XShmSegmentInfo Shminfo[MAX_BUFFERS];
int Shm_Warned_Slow;
-#endif
};
#define MP_FOURCC(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((unsigned)(d)<<24))
@@ -542,7 +538,6 @@ static bool allocate_xvimage(struct vo *vo, int foo)
int aligned_w = FFALIGN(ctx->image_width, 32);
// round up the height to next chroma boundary too
int aligned_h = FFALIGN(ctx->image_height, 2);
-#if HAVE_SHM
if (x11->display_is_local && XShmQueryExtension(x11->display)) {
ctx->Shmem_Flag = 1;
x11->ShmCompletionEvent = XShmGetEventBase(x11->display)
@@ -572,9 +567,7 @@ static bool allocate_xvimage(struct vo *vo, int foo)
XShmAttach(x11->display, &ctx->Shminfo[foo]);
XSync(x11->display, False);
shmctl(ctx->Shminfo[foo].shmid, IPC_RMID, 0);
- } else
-#endif
- {
+ } else {
ctx->xvimage[foo] =
(XvImage *) XvCreateImage(x11->display, ctx->xv_port,
ctx->xv_format, NULL, aligned_w,
@@ -604,22 +597,17 @@ static bool allocate_xvimage(struct vo *vo, int foo)
static void deallocate_xvimage(struct vo *vo, int foo)
{
struct xvctx *ctx = vo->priv;
-#if HAVE_SHM
if (ctx->Shmem_Flag) {
XShmDetach(vo->x11->display, &ctx->Shminfo[foo]);
shmdt(ctx->Shminfo[foo].shmaddr);
- } else
-#endif
- {
+ } else {
av_free(ctx->xvimage[foo]->data);
}
if (ctx->xvimage[foo])
XFree(ctx->xvimage[foo]);
ctx->xvimage[foo] = NULL;
-#if HAVE_SHM
ctx->Shminfo[foo] = (XShmSegmentInfo){0};
-#endif
XSync(vo->x11->display, False);
return;
@@ -633,16 +621,14 @@ static inline void put_xvimage(struct vo *vo, XvImage *xvi)
struct mp_rect *dst = &ctx->dst_rect;
int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0;
int sw = src->x1 - src->x0, sh = src->y1 - src->y0;
-#if HAVE_SHM
+
if (ctx->Shmem_Flag) {
XvShmPutImage(x11->display, ctx->xv_port, x11->window, ctx->vo_gc, xvi,
src->x0, src->y0, sw, sh,
dst->x0, dst->y0, dw, dh,
True);
x11->ShmCompletionWaitCount++;
- } else
-#endif
- {
+ } else {
XvPutImage(x11->display, ctx->xv_port, x11->window, ctx->vo_gc, xvi,
src->x0, src->y0, sw, sh,
dst->x0, dst->y0, dw, dh);
@@ -677,7 +663,6 @@ static struct mp_image get_xv_buffer(struct vo *vo, int buf_index)
static void wait_for_completion(struct vo *vo, int max_outstanding)
{
-#if HAVE_SHM
struct xvctx *ctx = vo->priv;
struct vo_x11_state *x11 = vo->x11;
if (ctx->Shmem_Flag) {
@@ -691,7 +676,6 @@ static void wait_for_completion(struct vo *vo, int max_outstanding)
vo_x11_check_events(vo);
}
}
-#endif
}
static void flip_page(struct vo *vo)
diff --git a/video/out/vulkan/common.h b/video/out/vulkan/common.h
new file mode 100644
index 0000000..6e82bfa
--- /dev/null
+++ b/video/out/vulkan/common.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include "config.h"
+
+#include "common/common.h"
+#include "common/msg.h"
+
+// We need to define all platforms we want to support. Since we have
+// our own mechanism for checking this, we re-define the right symbols
+#if HAVE_WAYLAND
+#define VK_USE_PLATFORM_WAYLAND_KHR
+#endif
+#if HAVE_X11
+#define VK_USE_PLATFORM_XLIB_KHR
+#endif
+#if HAVE_WIN32_DESKTOP
+#define VK_USE_PLATFORM_WIN32_KHR
+#endif
+
+#include <vulkan/vulkan.h>
+
+// Vulkan allows the optional use of a custom allocator. We don't need one but
+// mark this parameter with a better name in case we ever decide to change this
+// in the future. (And to make the code more readable)
+#define MPVK_ALLOCATOR NULL
+
+// A lot of things depend on streaming resources across frames. Depending on
+// how many frames we render ahead of time, we need to pick enough to avoid
+// any conflicts, so make all of these tunable relative to this constant in
+// order to centralize them.
+#define MPVK_MAX_STREAMING_DEPTH 8
+
+// Shared struct used to hold vulkan context information
+struct mpvk_ctx {
+ struct mp_log *log;
+ VkInstance inst;
+ VkPhysicalDevice physd;
+ VkDebugReportCallbackEXT dbg;
+ VkDevice dev;
+
+ // Surface, must be initialized fter the context itself
+ VkSurfaceKHR surf;
+ VkSurfaceFormatKHR surf_format; // picked at surface initialization time
+
+ struct vk_malloc *alloc; // memory allocator for this device
+ struct vk_cmdpool *pool; // primary command pool for this device
+ struct vk_cmd *last_cmd; // most recently submitted command
+ struct spirv_compiler *spirv; // GLSL -> SPIR-V compiler
+
+ // Cached capabilities
+ VkPhysicalDeviceLimits limits;
+};
diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c
new file mode 100644
index 0000000..0bca198
--- /dev/null
+++ b/video/out/vulkan/context.c
@@ -0,0 +1,518 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "options/m_config.h"
+#include "video/out/gpu/spirv.h"
+
+#include "context.h"
+#include "ra_vk.h"
+#include "utils.h"
+
+enum {
+ SWAP_AUTO = 0,
+ SWAP_FIFO,
+ SWAP_FIFO_RELAXED,
+ SWAP_MAILBOX,
+ SWAP_IMMEDIATE,
+ SWAP_COUNT,
+};
+
+struct vulkan_opts {
+ struct mpvk_device_opts dev_opts; // logical device options
+ char *device; // force a specific GPU
+ int swap_mode;
+};
+
+static int vk_validate_dev(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, struct bstr param)
+{
+ int ret = M_OPT_INVALID;
+ VkResult res;
+
+ // Create a dummy instance to validate/list the devices
+ VkInstanceCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ };
+
+ VkInstance inst;
+ VkPhysicalDevice *devices = NULL;
+ uint32_t num = 0;
+
+ res = vkCreateInstance(&info, MPVK_ALLOCATOR, &inst);
+ if (res != VK_SUCCESS)
+ goto error;
+
+ res = vkEnumeratePhysicalDevices(inst, &num, NULL);
+ if (res != VK_SUCCESS)
+ goto error;
+
+ devices = talloc_array(NULL, VkPhysicalDevice, num);
+ vkEnumeratePhysicalDevices(inst, &num, devices);
+ if (res != VK_SUCCESS)
+ goto error;
+
+ bool help = bstr_equals0(param, "help");
+ if (help) {
+ mp_info(log, "Available vulkan devices:\n");
+ ret = M_OPT_EXIT;
+ }
+
+ for (int i = 0; i < num; i++) {
+ VkPhysicalDeviceProperties prop;
+ vkGetPhysicalDeviceProperties(devices[i], &prop);
+
+ if (help) {
+ mp_info(log, " '%s' (GPU %d, ID %x:%x)\n", prop.deviceName, i,
+ (unsigned)prop.vendorID, (unsigned)prop.deviceID);
+ } else if (bstr_equals0(param, prop.deviceName)) {
+ ret = 0;
+ break;
+ }
+ }
+
+ if (!help)
+ mp_err(log, "No device with name '%.*s'!\n", BSTR_P(param));
+
+error:
+ talloc_free(devices);
+ return ret;
+}
+
+#define OPT_BASE_STRUCT struct vulkan_opts
+const struct m_sub_options vulkan_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_STRING_VALIDATE("vulkan-device", device, 0, vk_validate_dev),
+ OPT_CHOICE("vulkan-swap-mode", swap_mode, 0,
+ ({"auto", SWAP_AUTO},
+ {"fifo", SWAP_FIFO},
+ {"fifo-relaxed", SWAP_FIFO_RELAXED},
+ {"mailbox", SWAP_MAILBOX},
+ {"immediate", SWAP_IMMEDIATE})),
+ OPT_INTRANGE("vulkan-queue-count", dev_opts.queue_count, 0, 1,
+ MPVK_MAX_QUEUES, OPTDEF_INT(1)),
+ {0}
+ },
+ .size = sizeof(struct vulkan_opts)
+};
+
+struct priv {
+ struct mpvk_ctx *vk;
+ struct vulkan_opts *opts;
+ // Swapchain metadata:
+ int w, h; // current size
+ VkSwapchainCreateInfoKHR protoInfo; // partially filled-in prototype
+ VkSwapchainKHR swapchain;
+ VkSwapchainKHR old_swapchain;
+ int frames_in_flight;
+ // state of the images:
+ struct ra_tex **images; // ra_tex wrappers for the vkimages
+ int num_images; // size of images
+ VkSemaphore *acquired; // pool of semaphores used to synchronize images
+ int num_acquired; // size of this pool
+ int idx_acquired; // index of next free semaphore within this pool
+ int last_imgidx; // the image index last acquired (for submit)
+};
+
+static const struct ra_swapchain_fns vulkan_swapchain;
+
+struct mpvk_ctx *ra_vk_ctx_get(struct ra_ctx *ctx)
+{
+ if (ctx->swapchain->fns != &vulkan_swapchain)
+ return NULL;
+
+ struct priv *p = ctx->swapchain->priv;
+ return p->vk;
+}
+
+static bool update_swapchain_info(struct priv *p,
+ VkSwapchainCreateInfoKHR *info)
+{
+ struct mpvk_ctx *vk = p->vk;
+
+ // Query the supported capabilities and update this struct as needed
+ VkSurfaceCapabilitiesKHR caps;
+ VK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk->physd, vk->surf, &caps));
+
+ // Sorted by preference
+ static const VkCompositeAlphaFlagsKHR alphaModes[] = {
+ VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
+ VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ };
+
+ for (int i = 0; i < MP_ARRAY_SIZE(alphaModes); i++) {
+ if (caps.supportedCompositeAlpha & alphaModes[i]) {
+ info->compositeAlpha = alphaModes[i];
+ break;
+ }
+ }
+
+ if (!info->compositeAlpha) {
+ MP_ERR(vk, "Failed picking alpha compositing mode (caps: 0x%x)\n",
+ caps.supportedCompositeAlpha);
+ goto error;
+ }
+
+ static const VkSurfaceTransformFlagsKHR rotModes[] = {
+ VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+ VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR,
+ };
+
+ for (int i = 0; i < MP_ARRAY_SIZE(rotModes); i++) {
+ if (caps.supportedTransforms & rotModes[i]) {
+ info->preTransform = rotModes[i];
+ break;
+ }
+ }
+
+ if (!info->preTransform) {
+ MP_ERR(vk, "Failed picking surface transform mode (caps: 0x%x)\n",
+ caps.supportedTransforms);
+ goto error;
+ }
+
+ // Image count as required
+ MP_VERBOSE(vk, "Requested image count: %d (min %d max %d)\n",
+ (int)info->minImageCount, (int)caps.minImageCount,
+ (int)caps.maxImageCount);
+
+ info->minImageCount = MPMAX(info->minImageCount, caps.minImageCount);
+ if (caps.maxImageCount)
+ info->minImageCount = MPMIN(info->minImageCount, caps.maxImageCount);
+
+ // Check the extent against the allowed parameters
+ if (caps.currentExtent.width != info->imageExtent.width &&
+ caps.currentExtent.width != 0xFFFFFFFF)
+ {
+ MP_WARN(vk, "Requested width %d does not match current width %d\n",
+ (int)info->imageExtent.width, (int)caps.currentExtent.width);
+ info->imageExtent.width = caps.currentExtent.width;
+ }
+
+ if (caps.currentExtent.height != info->imageExtent.height &&
+ caps.currentExtent.height != 0xFFFFFFFF)
+ {
+ MP_WARN(vk, "Requested height %d does not match current height %d\n",
+ (int)info->imageExtent.height, (int)caps.currentExtent.height);
+ info->imageExtent.height = caps.currentExtent.height;
+ }
+
+ if (caps.minImageExtent.width > info->imageExtent.width ||
+ caps.minImageExtent.height > info->imageExtent.height)
+ {
+ MP_ERR(vk, "Requested size %dx%d smaller than device minimum %d%d\n",
+ (int)info->imageExtent.width, (int)info->imageExtent.height,
+ (int)caps.minImageExtent.width, (int)caps.minImageExtent.height);
+ goto error;
+ }
+
+ if (caps.maxImageExtent.width < info->imageExtent.width ||
+ caps.maxImageExtent.height < info->imageExtent.height)
+ {
+ MP_ERR(vk, "Requested size %dx%d larger than device maximum %d%d\n",
+ (int)info->imageExtent.width, (int)info->imageExtent.height,
+ (int)caps.maxImageExtent.width, (int)caps.maxImageExtent.height);
+ goto error;
+ }
+
+ // We just request whatever usage we can, and let the ra_vk decide what
+ // ra_tex_params that translates to. This makes the images as flexible
+ // as possible.
+ info->imageUsage = caps.supportedUsageFlags;
+ return true;
+
+error:
+ return false;
+}
+
+void ra_vk_ctx_uninit(struct ra_ctx *ctx)
+{
+ if (ctx->ra) {
+ struct priv *p = ctx->swapchain->priv;
+ struct mpvk_ctx *vk = p->vk;
+
+ mpvk_pool_wait_idle(vk, vk->pool);
+
+ for (int i = 0; i < p->num_images; i++)
+ ra_tex_free(ctx->ra, &p->images[i]);
+ for (int i = 0; i < p->num_acquired; i++)
+ vkDestroySemaphore(vk->dev, p->acquired[i], MPVK_ALLOCATOR);
+
+ vkDestroySwapchainKHR(vk->dev, p->swapchain, MPVK_ALLOCATOR);
+
+ talloc_free(p->images);
+ talloc_free(p->acquired);
+ ctx->ra->fns->destroy(ctx->ra);
+ ctx->ra = NULL;
+ }
+
+ talloc_free(ctx->swapchain);
+ ctx->swapchain = NULL;
+}
+
+static const struct ra_swapchain_fns vulkan_swapchain;
+
+bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
+ VkPresentModeKHR preferred_mode)
+{
+ struct ra_swapchain *sw = ctx->swapchain = talloc_zero(NULL, struct ra_swapchain);
+ sw->ctx = ctx;
+ sw->fns = &vulkan_swapchain;
+
+ struct priv *p = sw->priv = talloc_zero(sw, struct priv);
+ p->vk = vk;
+ p->opts = mp_get_config_group(p, ctx->global, &vulkan_conf);
+
+ if (!mpvk_find_phys_device(vk, p->opts->device, ctx->opts.allow_sw))
+ goto error;
+ if (!spirv_compiler_init(ctx))
+ goto error;
+ vk->spirv = ctx->spirv;
+ if (!mpvk_pick_surface_format(vk))
+ goto error;
+ if (!mpvk_device_init(vk, p->opts->dev_opts))
+ goto error;
+
+ ctx->ra = ra_create_vk(vk, ctx->log);
+ if (!ctx->ra)
+ goto error;
+
+ static const VkPresentModeKHR present_modes[SWAP_COUNT] = {
+ [SWAP_FIFO] = VK_PRESENT_MODE_FIFO_KHR,
+ [SWAP_FIFO_RELAXED] = VK_PRESENT_MODE_FIFO_RELAXED_KHR,
+ [SWAP_MAILBOX] = VK_PRESENT_MODE_MAILBOX_KHR,
+ [SWAP_IMMEDIATE] = VK_PRESENT_MODE_IMMEDIATE_KHR,
+ };
+
+ p->protoInfo = (VkSwapchainCreateInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = vk->surf,
+ .imageFormat = vk->surf_format.format,
+ .imageColorSpace = vk->surf_format.colorSpace,
+ .imageArrayLayers = 1, // non-stereoscopic
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .minImageCount = ctx->opts.swapchain_depth + 1, // +1 for FB
+ .presentMode = p->opts->swap_mode ? present_modes[p->opts->swap_mode]
+ : preferred_mode,
+ .clipped = true,
+ };
+
+ // Make sure the swapchain present mode is supported
+ int num_modes;
+ VK(vkGetPhysicalDeviceSurfacePresentModesKHR(vk->physd, vk->surf,
+ &num_modes, NULL));
+ VkPresentModeKHR *modes = talloc_array(NULL, VkPresentModeKHR, num_modes);
+ VK(vkGetPhysicalDeviceSurfacePresentModesKHR(vk->physd, vk->surf,
+ &num_modes, modes));
+ bool supported = false;
+ for (int i = 0; i < num_modes; i++)
+ supported |= (modes[i] == p->protoInfo.presentMode);
+ talloc_free(modes);
+
+ if (!supported) {
+ MP_ERR(ctx, "Requested swap mode unsupported by this device!\n");
+ goto error;
+ }
+
+ return true;
+
+error:
+ ra_vk_ctx_uninit(ctx);
+ return false;
+}
+
+static void destroy_swapchain(struct mpvk_ctx *vk, struct priv *p)
+{
+ assert(p->old_swapchain);
+ vkDestroySwapchainKHR(vk->dev, p->old_swapchain, MPVK_ALLOCATOR);
+ p->old_swapchain = NULL;
+}
+
+bool ra_vk_ctx_resize(struct ra_swapchain *sw, int w, int h)
+{
+ struct priv *p = sw->priv;
+ if (w == p->w && h == p->h)
+ return true;
+
+ struct ra *ra = sw->ctx->ra;
+ struct mpvk_ctx *vk = p->vk;
+ VkImage *vkimages = NULL;
+
+ // It's invalid to trigger another swapchain recreation while there's
+ // more than one swapchain already active, so we need to flush any pending
+ // asynchronous swapchain release operations that may be ongoing.
+ while (p->old_swapchain)
+ mpvk_dev_poll_cmds(vk, 100000); // 100μs
+
+ VkSwapchainCreateInfoKHR sinfo = p->protoInfo;
+ sinfo.imageExtent = (VkExtent2D){ w, h };
+ sinfo.oldSwapchain = p->swapchain;
+
+ if (!update_swapchain_info(p, &sinfo))
+ goto error;
+
+ VK(vkCreateSwapchainKHR(vk->dev, &sinfo, MPVK_ALLOCATOR, &p->swapchain));
+ p->w = w;
+ p->h = h;
+
+ // Freeing the old swapchain while it's still in use is an error, so do
+ // it asynchronously once the device is idle.
+ if (sinfo.oldSwapchain) {
+ p->old_swapchain = sinfo.oldSwapchain;
+ vk_dev_callback(vk, (vk_cb) destroy_swapchain, vk, p);
+ }
+
+ // Get the new swapchain images
+ int num;
+ VK(vkGetSwapchainImagesKHR(vk->dev, p->swapchain, &num, NULL));
+ vkimages = talloc_array(NULL, VkImage, num);
+ VK(vkGetSwapchainImagesKHR(vk->dev, p->swapchain, &num, vkimages));
+
+ // If needed, allocate some more semaphores
+ while (num > p->num_acquired) {
+ VkSemaphore sem;
+ static const VkSemaphoreCreateInfo seminfo = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ };
+ VK(vkCreateSemaphore(vk->dev, &seminfo, MPVK_ALLOCATOR, &sem));
+ MP_TARRAY_APPEND(NULL, p->acquired, p->num_acquired, sem);
+ }
+
+ // Recreate the ra_tex wrappers
+ for (int i = 0; i < p->num_images; i++)
+ ra_tex_free(ra, &p->images[i]);
+
+ p->num_images = num;
+ MP_TARRAY_GROW(NULL, p->images, p->num_images);
+ for (int i = 0; i < num; i++) {
+ p->images[i] = ra_vk_wrap_swapchain_img(ra, vkimages[i], sinfo);
+ if (!p->images[i])
+ goto error;
+ }
+
+ talloc_free(vkimages);
+ return true;
+
+error:
+ talloc_free(vkimages);
+ vkDestroySwapchainKHR(vk->dev, p->swapchain, MPVK_ALLOCATOR);
+ p->swapchain = NULL;
+ return false;
+}
+
+static int color_depth(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+ int bits = 0;
+
+ if (!p->num_images)
+ return bits;
+
+ // The channel with the most bits is probably the most authoritative about
+ // the actual color information (consider e.g. a2bgr10). Slight downside
+ // in that it results in rounding r/b for e.g. rgb565, but we don't pick
+ // surfaces with fewer than 8 bits anyway.
+ const struct ra_format *fmt = p->images[0]->params.format;
+ for (int i = 0; i < fmt->num_components; i++) {
+ int depth = fmt->component_depth[i];
+ bits = MPMAX(bits, depth ? depth : fmt->component_size[i]);
+ }
+
+ return bits;
+}
+
+static bool start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
+{
+ struct priv *p = sw->priv;
+ struct mpvk_ctx *vk = p->vk;
+ if (!p->swapchain)
+ goto error;
+
+ uint32_t imgidx = 0;
+ MP_TRACE(vk, "vkAcquireNextImageKHR\n");
+ VkResult res = vkAcquireNextImageKHR(vk->dev, p->swapchain, UINT64_MAX,
+ p->acquired[p->idx_acquired], NULL,
+ &imgidx);
+ if (res == VK_ERROR_OUT_OF_DATE_KHR)
+ goto error; // just return in this case
+ VK_ASSERT(res, "Failed acquiring swapchain image");
+
+ p->last_imgidx = imgidx;
+ *out_fbo = (struct ra_fbo) {
+ .tex = p->images[imgidx],
+ .flip = false,
+ };
+ return true;
+
+error:
+ return false;
+}
+
+static bool submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
+{
+ struct priv *p = sw->priv;
+ struct ra *ra = sw->ctx->ra;
+ struct mpvk_ctx *vk = p->vk;
+ if (!p->swapchain)
+ goto error;
+
+ VkSemaphore acquired = p->acquired[p->idx_acquired++];
+ p->idx_acquired %= p->num_acquired;
+
+ VkSemaphore done;
+ if (!ra_vk_submit(ra, p->images[p->last_imgidx], acquired, &done,
+ &p->frames_in_flight))
+ goto error;
+
+ // Older nvidia drivers can spontaneously combust when submitting to the
+ // same queue as we're rendering from, in a multi-queue scenario. Safest
+ // option is to cycle the queues first and then submit to the next queue.
+ // We can drop this hack in the future, I suppose.
+ vk_cmd_cycle_queues(vk);
+ struct vk_cmdpool *pool = vk->pool;
+ VkQueue queue = pool->queues[pool->qindex];
+
+ VkPresentInfoKHR pinfo = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &done,
+ .swapchainCount = 1,
+ .pSwapchains = &p->swapchain,
+ .pImageIndices = &p->last_imgidx,
+ };
+
+ VK(vkQueuePresentKHR(queue, &pinfo));
+ return true;
+
+error:
+ return false;
+}
+
+static void swap_buffers(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+
+ while (p->frames_in_flight >= sw->ctx->opts.swapchain_depth)
+ mpvk_dev_poll_cmds(p->vk, 100000); // 100μs
+}
+
+static const struct ra_swapchain_fns vulkan_swapchain = {
+ // .screenshot is not currently supported
+ .color_depth = color_depth,
+ .start_frame = start_frame,
+ .submit_frame = submit_frame,
+ .swap_buffers = swap_buffers,
+};
diff --git a/video/out/vulkan/context.h b/video/out/vulkan/context.h
new file mode 100644
index 0000000..a64d39f
--- /dev/null
+++ b/video/out/vulkan/context.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "video/out/gpu/context.h"
+#include "common.h"
+
+// Helpers for ra_ctx based on ra_vk. These initialize ctx->ra and ctx->swchain.
+void ra_vk_ctx_uninit(struct ra_ctx *ctx);
+bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
+ VkPresentModeKHR preferred_mode);
+bool ra_vk_ctx_resize(struct ra_swapchain *sw, int w, int h);
+
+// May be called on a ra_ctx of any type.
+struct mpvk_ctx *ra_vk_ctx_get(struct ra_ctx *ctx);
diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c
new file mode 100644
index 0000000..7276775
--- /dev/null
+++ b/video/out/vulkan/context_wayland.c
@@ -0,0 +1,133 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "video/out/wayland_common.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static void wayland_vk_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ vo_wayland_uninit(ctx->vo);
+}
+
+static bool wayland_vk_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_instance_init(vk, ctx->log, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
+ ctx->opts.debug))
+ goto error;
+
+ if (!vo_wayland_init(ctx->vo))
+ goto error;
+
+ VkWaylandSurfaceCreateInfoKHR wlinfo = {
+ .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
+ .display = ctx->vo->wl->display,
+ .surface = ctx->vo->wl->surface,
+ };
+
+ VkResult res = vkCreateWaylandSurfaceKHR(vk->inst, &wlinfo, MPVK_ALLOCATOR,
+ &vk->surf);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Wayland surface: %s\n", vk_err(res));
+ goto error;
+ }
+
+ /* Because in Wayland clients render whenever they receive a callback from
+ * the compositor, and the fact that the compositor usually stops sending
+ * callbacks once the surface is no longer visible, using FIFO here would
+ * mean the entire player would block on acquiring swapchain images. Hence,
+ * use MAILBOX to guarantee that there'll always be a swapchain image and
+ * the player won't block waiting on those */
+ if (!ra_vk_ctx_init(ctx, vk, VK_PRESENT_MODE_MAILBOX_KHR))
+ goto error;
+
+ return true;
+
+error:
+ wayland_vk_uninit(ctx);
+ return false;
+}
+
+static void resize(struct ra_ctx *ctx)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ MP_VERBOSE(wl, "Handling resize on the vk side\n");
+
+ const int32_t width = wl->scaling*mp_rect_w(wl->geometry);
+ const int32_t height = wl->scaling*mp_rect_h(wl->geometry);
+
+ wl_surface_set_buffer_scale(wl->surface, wl->scaling);
+
+ wl->vo->dwidth = width;
+ wl->vo->dheight = height;
+}
+
+static bool wayland_vk_reconfig(struct ra_ctx *ctx)
+{
+ if (!vo_wayland_reconfig(ctx->vo))
+ return false;
+
+ return true;
+}
+
+static int wayland_vk_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_wayland_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ resize(ctx);
+ if (ra_vk_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+static void wayland_vk_wakeup(struct ra_ctx *ctx)
+{
+ vo_wayland_wakeup(ctx->vo);
+}
+
+static void wayland_vk_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
+{
+ vo_wayland_wait_events(ctx->vo, until_time_us);
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_wayland = {
+ .type = "vulkan",
+ .name = "waylandvk",
+ .reconfig = wayland_vk_reconfig,
+ .control = wayland_vk_control,
+ .wakeup = wayland_vk_wakeup,
+ .wait_events = wayland_vk_wait_events,
+ .init = wayland_vk_init,
+ .uninit = wayland_vk_uninit,
+};
diff --git a/video/out/vulkan/context_win.c b/video/out/vulkan/context_win.c
new file mode 100644
index 0000000..cf31586
--- /dev/null
+++ b/video/out/vulkan/context_win.c
@@ -0,0 +1,105 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "video/out/w32_common.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+EXTERN_C IMAGE_DOS_HEADER __ImageBase;
+#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static void win_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ vo_w32_uninit(ctx->vo);
+}
+
+static bool win_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_instance_init(vk, ctx->log, VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
+ ctx->opts.debug))
+ goto error;
+
+ if (!vo_w32_init(ctx->vo))
+ goto error;
+
+ VkWin32SurfaceCreateInfoKHR wininfo = {
+ .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
+ .hinstance = HINST_THISCOMPONENT,
+ .hwnd = vo_w32_hwnd(ctx->vo),
+ };
+
+ VkResult res = vkCreateWin32SurfaceKHR(vk->inst, &wininfo, MPVK_ALLOCATOR,
+ &vk->surf);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Windows surface: %s\n", vk_err(res));
+ goto error;
+ }
+
+ if (!ra_vk_ctx_init(ctx, vk, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+ return true;
+
+error:
+ win_uninit(ctx);
+ return false;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ return ra_vk_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight);
+}
+
+static bool win_reconfig(struct ra_ctx *ctx)
+{
+ vo_w32_config(ctx->vo);
+ return resize(ctx);
+}
+
+static int win_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_w32_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_win = {
+ .type = "vulkan",
+ .name = "winvk",
+ .reconfig = win_reconfig,
+ .control = win_control,
+ .init = win_init,
+ .uninit = win_uninit,
+};
diff --git a/video/out/vulkan/context_xlib.c b/video/out/vulkan/context_xlib.c
new file mode 100644
index 0000000..c3bd49f
--- /dev/null
+++ b/video/out/vulkan/context_xlib.c
@@ -0,0 +1,117 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "video/out/x11_common.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static void xlib_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ vo_x11_uninit(ctx->vo);
+}
+
+static bool xlib_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_instance_init(vk, ctx->log, VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
+ ctx->opts.debug))
+ goto error;
+
+ if (!vo_x11_init(ctx->vo))
+ goto error;
+
+ if (!vo_x11_create_vo_window(ctx->vo, NULL, "mpvk"))
+ goto error;
+
+ VkXlibSurfaceCreateInfoKHR xinfo = {
+ .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
+ .dpy = ctx->vo->x11->display,
+ .window = ctx->vo->x11->window,
+ };
+
+ VkResult res = vkCreateXlibSurfaceKHR(vk->inst, &xinfo, MPVK_ALLOCATOR,
+ &vk->surf);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Xlib surface: %s\n", vk_err(res));
+ goto error;
+ }
+
+ if (!ra_vk_ctx_init(ctx, vk, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+ return true;
+
+error:
+ xlib_uninit(ctx);
+ return false;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ return ra_vk_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight);
+}
+
+static bool xlib_reconfig(struct ra_ctx *ctx)
+{
+ vo_x11_config_vo_window(ctx->vo);
+ return resize(ctx);
+}
+
+static int xlib_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_x11_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+static void xlib_wakeup(struct ra_ctx *ctx)
+{
+ vo_x11_wakeup(ctx->vo);
+}
+
+static void xlib_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
+{
+ vo_x11_wait_events(ctx->vo, until_time_us);
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_xlib = {
+ .type = "vulkan",
+ .name = "x11vk",
+ .reconfig = xlib_reconfig,
+ .control = xlib_control,
+ .wakeup = xlib_wakeup,
+ .wait_events = xlib_wait_events,
+ .init = xlib_init,
+ .uninit = xlib_uninit,
+};
diff --git a/video/out/vulkan/formats.c b/video/out/vulkan/formats.c
new file mode 100644
index 0000000..b44bead
--- /dev/null
+++ b/video/out/vulkan/formats.c
@@ -0,0 +1,55 @@
+#include "formats.h"
+
+const struct vk_format vk_formats[] = {
+ // Regular, byte-aligned integer formats
+ {"r8", VK_FORMAT_R8_UNORM, 1, 1, {8 }, RA_CTYPE_UNORM },
+ {"rg8", VK_FORMAT_R8G8_UNORM, 2, 2, {8, 8 }, RA_CTYPE_UNORM },
+ {"rgb8", VK_FORMAT_R8G8B8_UNORM, 3, 3, {8, 8, 8 }, RA_CTYPE_UNORM },
+ {"rgba8", VK_FORMAT_R8G8B8A8_UNORM, 4, 4, {8, 8, 8, 8 }, RA_CTYPE_UNORM },
+ {"r16", VK_FORMAT_R16_UNORM, 1, 2, {16 }, RA_CTYPE_UNORM },
+ {"rg16", VK_FORMAT_R16G16_UNORM, 2, 4, {16, 16 }, RA_CTYPE_UNORM },
+ {"rgb16", VK_FORMAT_R16G16B16_UNORM, 3, 6, {16, 16, 16 }, RA_CTYPE_UNORM },
+ {"rgba16", VK_FORMAT_R16G16B16A16_UNORM, 4, 8, {16, 16, 16, 16}, RA_CTYPE_UNORM },
+
+ // Special, integer-only formats
+ {"r32ui", VK_FORMAT_R32_UINT, 1, 4, {32 }, RA_CTYPE_UINT },
+ {"rg32ui", VK_FORMAT_R32G32_UINT, 2, 8, {32, 32 }, RA_CTYPE_UINT },
+ {"rgb32ui", VK_FORMAT_R32G32B32_UINT, 3, 12, {32, 32, 32 }, RA_CTYPE_UINT },
+ {"rgba32ui", VK_FORMAT_R32G32B32A32_UINT, 4, 16, {32, 32, 32, 32}, RA_CTYPE_UINT },
+ {"r64ui", VK_FORMAT_R64_UINT, 1, 8, {64 }, RA_CTYPE_UINT },
+ {"rg64ui", VK_FORMAT_R64G64_UINT, 2, 16, {64, 64 }, RA_CTYPE_UINT },
+ {"rgb64ui", VK_FORMAT_R64G64B64_UINT, 3, 24, {64, 64, 64 }, RA_CTYPE_UINT },
+ {"rgba64ui", VK_FORMAT_R64G64B64A64_UINT, 4, 32, {64, 64, 64, 64}, RA_CTYPE_UINT },
+
+ // Packed integer formats
+ {"rg4", VK_FORMAT_R4G4_UNORM_PACK8, 2, 1, {4, 4 }, RA_CTYPE_UNORM },
+ {"rgba4", VK_FORMAT_R4G4B4A4_UNORM_PACK16, 4, 2, {4, 4, 4, 4 }, RA_CTYPE_UNORM },
+ {"rgb565", VK_FORMAT_R5G6B5_UNORM_PACK16, 3, 2, {5, 6, 5 }, RA_CTYPE_UNORM },
+ {"rgb565a1", VK_FORMAT_R5G5B5A1_UNORM_PACK16, 4, 2, {5, 5, 5, 1 }, RA_CTYPE_UNORM },
+
+ // Float formats (native formats, hf = half float, df = double float)
+ {"r16hf", VK_FORMAT_R16_SFLOAT, 1, 2, {16 }, RA_CTYPE_FLOAT },
+ {"rg16hf", VK_FORMAT_R16G16_SFLOAT, 2, 4, {16, 16 }, RA_CTYPE_FLOAT },
+ {"rgb16hf", VK_FORMAT_R16G16B16_SFLOAT, 3, 6, {16, 16, 16 }, RA_CTYPE_FLOAT },
+ {"rgba16hf", VK_FORMAT_R16G16B16A16_SFLOAT, 4, 8, {16, 16, 16, 16}, RA_CTYPE_FLOAT },
+ {"r32f", VK_FORMAT_R32_SFLOAT, 1, 4, {32 }, RA_CTYPE_FLOAT },
+ {"rg32f", VK_FORMAT_R32G32_SFLOAT, 2, 8, {32, 32 }, RA_CTYPE_FLOAT },
+ {"rgb32f", VK_FORMAT_R32G32B32_SFLOAT, 3, 12, {32, 32, 32 }, RA_CTYPE_FLOAT },
+ {"rgba32f", VK_FORMAT_R32G32B32A32_SFLOAT, 4, 16, {32, 32, 32, 32}, RA_CTYPE_FLOAT },
+ {"r64df", VK_FORMAT_R64_SFLOAT, 1, 8, {64 }, RA_CTYPE_FLOAT },
+ {"rg64df", VK_FORMAT_R64G64_SFLOAT, 2, 16, {64, 64 }, RA_CTYPE_FLOAT },
+ {"rgb64df", VK_FORMAT_R64G64B64_SFLOAT, 3, 24, {64, 64, 64 }, RA_CTYPE_FLOAT },
+ {"rgba64df", VK_FORMAT_R64G64B64A64_SFLOAT, 4, 32, {64, 64, 64, 64}, RA_CTYPE_FLOAT },
+
+ // "Swapped" component order images
+ {"bgr8", VK_FORMAT_B8G8R8_UNORM, 3, 3, {8, 8, 8 }, RA_CTYPE_UNORM, true },
+ {"bgra8", VK_FORMAT_B8G8R8A8_UNORM, 4, 4, {8, 8, 8, 8 }, RA_CTYPE_UNORM, true },
+ {"bgra4", VK_FORMAT_B4G4R4A4_UNORM_PACK16, 4, 2, {4, 4, 4, 4 }, RA_CTYPE_UNORM, true },
+ {"bgr565", VK_FORMAT_B5G6R5_UNORM_PACK16, 3, 2, {5, 6, 5 }, RA_CTYPE_UNORM, true },
+ {"bgr565a1", VK_FORMAT_B5G5R5A1_UNORM_PACK16, 4, 2, {5, 5, 5, 1 }, RA_CTYPE_UNORM, true },
+ {"a1rgb5", VK_FORMAT_A1R5G5B5_UNORM_PACK16, 4, 2, {1, 5, 5, 5 }, RA_CTYPE_UNORM, true },
+ {"a2rgb10", VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4, 4, {2, 10, 10, 10}, RA_CTYPE_UNORM, true },
+ {"a2bgr10", VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4, 4, {2, 10, 10, 10}, RA_CTYPE_UNORM, true },
+ {"abgr8", VK_FORMAT_A8B8G8R8_UNORM_PACK32, 4, 4, {8, 8, 8, 8 }, RA_CTYPE_UNORM, true },
+ {0}
+};
diff --git a/video/out/vulkan/formats.h b/video/out/vulkan/formats.h
new file mode 100644
index 0000000..22782a6
--- /dev/null
+++ b/video/out/vulkan/formats.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "video/out/gpu/ra.h"
+#include "common.h"
+
+struct vk_format {
+ const char *name;
+ VkFormat iformat; // vulkan format enum
+ int components; // how many components are there
+ int bytes; // how many bytes is a texel
+ int bits[4]; // how many bits per component
+ enum ra_ctype ctype; // format representation type
+ bool fucked_order; // used for formats which are not simply rgba
+};
+
+extern const struct vk_format vk_formats[];
diff --git a/video/out/vulkan/malloc.c b/video/out/vulkan/malloc.c
new file mode 100644
index 0000000..f6cb114
--- /dev/null
+++ b/video/out/vulkan/malloc.c
@@ -0,0 +1,423 @@
+#include "malloc.h"
+#include "utils.h"
+#include "osdep/timer.h"
+
+// Controls the multiplication factor for new slab allocations. The new slab
+// will always be allocated such that the size of the slab is this factor times
+// the previous slab. Higher values make it grow faster.
+#define MPVK_HEAP_SLAB_GROWTH_RATE 4
+
+// Controls the minimum slab size, to reduce the frequency at which very small
+// slabs would need to get allocated when allocating the first few buffers.
+// (Default: 1 MB)
+#define MPVK_HEAP_MINIMUM_SLAB_SIZE (1 << 20)
+
+// Controls the maximum slab size, to reduce the effect of unbounded slab
+// growth exhausting memory. If the application needs a single allocation
+// that's bigger than this value, it will be allocated directly from the
+// device. (Default: 512 MB)
+#define MPVK_HEAP_MAXIMUM_SLAB_SIZE (1 << 29)
+
+// Controls the minimum free region size, to reduce thrashing the free space
+// map with lots of small buffers during uninit. (Default: 1 KB)
+#define MPVK_HEAP_MINIMUM_REGION_SIZE (1 << 10)
+
+// Represents a region of available memory
+struct vk_region {
+ size_t start; // first offset in region
+ size_t end; // first offset *not* in region
+};
+
+static inline size_t region_len(struct vk_region r)
+{
+ return r.end - r.start;
+}
+
+// A single slab represents a contiguous region of allocated memory. Actual
+// allocations are served as slices of this. Slabs are organized into linked
+// lists, which represent individual heaps.
+struct vk_slab {
+ VkDeviceMemory mem; // underlying device allocation
+ size_t size; // total size of `slab`
+ size_t used; // number of bytes actually in use (for GC accounting)
+ bool dedicated; // slab is allocated specifically for one object
+ // free space map: a sorted list of memory regions that are available
+ struct vk_region *regions;
+ int num_regions;
+ // optional, depends on the memory type:
+ VkBuffer buffer; // buffer spanning the entire slab
+ void *data; // mapped memory corresponding to `mem`
+};
+
+// Represents a single memory heap. We keep track of a vk_heap for each
+// combination of buffer type and memory selection parameters. This shouldn't
+// actually be that many in practice, because some combinations simply never
+// occur, and others will generally be the same for the same objects.
+struct vk_heap {
+ VkBufferUsageFlags usage; // the buffer usage type (or 0)
+ VkMemoryPropertyFlags flags; // the memory type flags (or 0)
+ uint32_t typeBits; // the memory type index requirements (or 0)
+ struct vk_slab **slabs; // array of slabs sorted by size
+ int num_slabs;
+};
+
+// The overall state of the allocator, which keeps track of a vk_heap for each
+// memory type.
+struct vk_malloc {
+ VkPhysicalDeviceMemoryProperties props;
+ struct vk_heap *heaps;
+ int num_heaps;
+};
+
+static void slab_free(struct mpvk_ctx *vk, struct vk_slab *slab)
+{
+ if (!slab)
+ return;
+
+ assert(slab->used == 0);
+
+ int64_t start = mp_time_us();
+ vkDestroyBuffer(vk->dev, slab->buffer, MPVK_ALLOCATOR);
+ // also implicitly unmaps the memory if needed
+ vkFreeMemory(vk->dev, slab->mem, MPVK_ALLOCATOR);
+ int64_t stop = mp_time_us();
+
+ MP_VERBOSE(vk, "Freeing slab of size %zu took %lld μs.\n",
+ slab->size, (long long)(stop - start));
+
+ talloc_free(slab);
+}
+
+static bool find_best_memtype(struct mpvk_ctx *vk, uint32_t typeBits,
+ VkMemoryPropertyFlags flags,
+ VkMemoryType *out_type, int *out_index)
+{
+ struct vk_malloc *ma = vk->alloc;
+
+ // The vulkan spec requires memory types to be sorted in the "optimal"
+ // order, so the first matching type we find will be the best/fastest one.
+ for (int i = 0; i < ma->props.memoryTypeCount; i++) {
+ // The memory type flags must include our properties
+ if ((ma->props.memoryTypes[i].propertyFlags & flags) != flags)
+ continue;
+ // The memory type must be supported by the requirements (bitfield)
+ if (typeBits && !(typeBits & (1 << i)))
+ continue;
+ *out_type = ma->props.memoryTypes[i];
+ *out_index = i;
+ return true;
+ }
+
+ MP_ERR(vk, "Found no memory type matching property flags 0x%x and type "
+ "bits 0x%x!\n", (unsigned)flags, (unsigned)typeBits);
+ return false;
+}
+
+static struct vk_slab *slab_alloc(struct mpvk_ctx *vk, struct vk_heap *heap,
+ size_t size)
+{
+ struct vk_slab *slab = talloc_ptrtype(NULL, slab);
+ *slab = (struct vk_slab) {
+ .size = size,
+ };
+
+ MP_TARRAY_APPEND(slab, slab->regions, slab->num_regions, (struct vk_region) {
+ .start = 0,
+ .end = slab->size,
+ });
+
+ VkMemoryAllocateInfo minfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = slab->size,
+ };
+
+ uint32_t typeBits = heap->typeBits ? heap->typeBits : UINT32_MAX;
+ if (heap->usage) {
+ VkBufferCreateInfo binfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = slab->size,
+ .usage = heap->usage,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ };
+
+ VK(vkCreateBuffer(vk->dev, &binfo, MPVK_ALLOCATOR, &slab->buffer));
+
+ VkMemoryRequirements reqs;
+ vkGetBufferMemoryRequirements(vk->dev, slab->buffer, &reqs);
+ minfo.allocationSize = reqs.size; // this can be larger than slab->size
+ typeBits &= reqs.memoryTypeBits; // this can restrict the types
+ }
+
+ VkMemoryType type;
+ int index;
+ if (!find_best_memtype(vk, typeBits, heap->flags, &type, &index))
+ goto error;
+
+ MP_VERBOSE(vk, "Allocating %zu memory of type 0x%x (id %d) in heap %d.\n",
+ slab->size, (unsigned)type.propertyFlags, index, (int)type.heapIndex);
+
+ minfo.memoryTypeIndex = index;
+ VK(vkAllocateMemory(vk->dev, &minfo, MPVK_ALLOCATOR, &slab->mem));
+
+ if (heap->flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
+ VK(vkMapMemory(vk->dev, slab->mem, 0, VK_WHOLE_SIZE, 0, &slab->data));
+
+ if (slab->buffer)
+ VK(vkBindBufferMemory(vk->dev, slab->buffer, slab->mem, 0));
+
+ return slab;
+
+error:
+ slab_free(vk, slab);
+ return NULL;
+}
+
+static void insert_region(struct vk_slab *slab, struct vk_region region)
+{
+ if (region.start == region.end)
+ return;
+
+ bool big_enough = region_len(region) >= MPVK_HEAP_MINIMUM_REGION_SIZE;
+
+ // Find the index of the first region that comes after this
+ for (int i = 0; i < slab->num_regions; i++) {
+ struct vk_region *r = &slab->regions[i];
+
+ // Check for a few special cases which can be coalesced
+ if (r->end == region.start) {
+ // The new region is at the tail of this region. In addition to
+ // modifying this region, we also need to coalesce all the following
+ // regions for as long as possible
+ r->end = region.end;
+
+ struct vk_region *next = &slab->regions[i+1];
+ while (i+1 < slab->num_regions && r->end == next->start) {
+ r->end = next->end;
+ MP_TARRAY_REMOVE_AT(slab->regions, slab->num_regions, i+1);
+ }
+ return;
+ }
+
+ if (r->start == region.end) {
+ // The new region is at the head of this region. We don't need to
+ // do anything special here - because if this could be further
+ // coalesced backwards, the previous loop iteration would already
+ // have caught it.
+ r->start = region.start;
+ return;
+ }
+
+ if (r->start > region.start) {
+ // The new region comes somewhere before this region, so insert
+ // it into this index in the array.
+ if (big_enough) {
+ MP_TARRAY_INSERT_AT(slab, slab->regions, slab->num_regions,
+ i, region);
+ }
+ return;
+ }
+ }
+
+ // If we've reached the end of this loop, then all of the regions
+ // come before the new region, and are disconnected - so append it
+ if (big_enough)
+ MP_TARRAY_APPEND(slab, slab->regions, slab->num_regions, region);
+}
+
+static void heap_uninit(struct mpvk_ctx *vk, struct vk_heap *heap)
+{
+ for (int i = 0; i < heap->num_slabs; i++)
+ slab_free(vk, heap->slabs[i]);
+
+ talloc_free(heap->slabs);
+ *heap = (struct vk_heap){0};
+}
+
+void vk_malloc_init(struct mpvk_ctx *vk)
+{
+ assert(vk->physd);
+ vk->alloc = talloc_zero(NULL, struct vk_malloc);
+ vkGetPhysicalDeviceMemoryProperties(vk->physd, &vk->alloc->props);
+}
+
+void vk_malloc_uninit(struct mpvk_ctx *vk)
+{
+ struct vk_malloc *ma = vk->alloc;
+ if (!ma)
+ return;
+
+ for (int i = 0; i < ma->num_heaps; i++)
+ heap_uninit(vk, &ma->heaps[i]);
+
+ talloc_free(ma);
+ vk->alloc = NULL;
+}
+
+void vk_free_memslice(struct mpvk_ctx *vk, struct vk_memslice slice)
+{
+ struct vk_slab *slab = slice.priv;
+ if (!slab)
+ return;
+
+ assert(slab->used >= slice.size);
+ slab->used -= slice.size;
+
+ MP_DBG(vk, "Freeing slice %zu + %zu from slab with size %zu\n",
+ slice.offset, slice.size, slab->size);
+
+ if (slab->dedicated) {
+ // If the slab was purpose-allocated for this memslice, we can just
+ // free it here
+ slab_free(vk, slab);
+ } else {
+ // Return the allocation to the free space map
+ insert_region(slab, (struct vk_region) {
+ .start = slice.offset,
+ .end = slice.offset + slice.size,
+ });
+ }
+}
+
+// reqs: can be NULL
+static struct vk_heap *find_heap(struct mpvk_ctx *vk, VkBufferUsageFlags usage,
+ VkMemoryPropertyFlags flags,
+ VkMemoryRequirements *reqs)
+{
+ struct vk_malloc *ma = vk->alloc;
+ int typeBits = reqs ? reqs->memoryTypeBits : 0;
+
+ for (int i = 0; i < ma->num_heaps; i++) {
+ if (ma->heaps[i].usage != usage)
+ continue;
+ if (ma->heaps[i].flags != flags)
+ continue;
+ if (ma->heaps[i].typeBits != typeBits)
+ continue;
+ return &ma->heaps[i];
+ }
+
+ // Not found => add it
+ MP_TARRAY_GROW(ma, ma->heaps, ma->num_heaps + 1);
+ struct vk_heap *heap = &ma->heaps[ma->num_heaps++];
+ *heap = (struct vk_heap) {
+ .usage = usage,
+ .flags = flags,
+ .typeBits = typeBits,
+ };
+ return heap;
+}
+
+static inline bool region_fits(struct vk_region r, size_t size, size_t align)
+{
+ return MP_ALIGN_UP(r.start, align) + size <= r.end;
+}
+
+// Finds the best-fitting region in a heap. If the heap is too small or too
+// fragmented, a new slab will be allocated under the hood.
+static bool heap_get_region(struct mpvk_ctx *vk, struct vk_heap *heap,
+ size_t size, size_t align,
+ struct vk_slab **out_slab, int *out_index)
+{
+ struct vk_slab *slab = NULL;
+
+ // If the allocation is very big, serve it directly instead of bothering
+ // with the heap
+ if (size > MPVK_HEAP_MAXIMUM_SLAB_SIZE) {
+ slab = slab_alloc(vk, heap, size);
+ *out_slab = slab;
+ *out_index = 0;
+ return !!slab;
+ }
+
+ for (int i = 0; i < heap->num_slabs; i++) {
+ slab = heap->slabs[i];
+ if (slab->size < size)
+ continue;
+
+ // Attempt a best fit search
+ int best = -1;
+ for (int n = 0; n < slab->num_regions; n++) {
+ struct vk_region r = slab->regions[n];
+ if (!region_fits(r, size, align))
+ continue;
+ if (best >= 0 && region_len(r) > region_len(slab->regions[best]))
+ continue;
+ best = n;
+ }
+
+ if (best >= 0) {
+ *out_slab = slab;
+ *out_index = best;
+ return true;
+ }
+ }
+
+ // Otherwise, allocate a new vk_slab and append it to the list.
+ size_t cur_size = MPMAX(size, slab ? slab->size : 0);
+ size_t slab_size = MPVK_HEAP_SLAB_GROWTH_RATE * cur_size;
+ slab_size = MPMAX(MPVK_HEAP_MINIMUM_SLAB_SIZE, slab_size);
+ slab_size = MPMIN(MPVK_HEAP_MAXIMUM_SLAB_SIZE, slab_size);
+ assert(slab_size >= size);
+ slab = slab_alloc(vk, heap, slab_size);
+ if (!slab)
+ return false;
+ MP_TARRAY_APPEND(NULL, heap->slabs, heap->num_slabs, slab);
+
+ // Return the only region there is in a newly allocated slab
+ assert(slab->num_regions == 1);
+ *out_slab = slab;
+ *out_index = 0;
+ return true;
+}
+
+static bool slice_heap(struct mpvk_ctx *vk, struct vk_heap *heap, size_t size,
+ size_t alignment, struct vk_memslice *out)
+{
+ struct vk_slab *slab;
+ int index;
+ alignment = MP_ALIGN_UP(alignment, vk->limits.bufferImageGranularity);
+ if (!heap_get_region(vk, heap, size, alignment, &slab, &index))
+ return false;
+
+ struct vk_region reg = slab->regions[index];
+ MP_TARRAY_REMOVE_AT(slab->regions, slab->num_regions, index);
+ *out = (struct vk_memslice) {
+ .vkmem = slab->mem,
+ .offset = MP_ALIGN_UP(reg.start, alignment),
+ .size = size,
+ .priv = slab,
+ };
+
+ MP_DBG(vk, "Sub-allocating slice %zu + %zu from slab with size %zu\n",
+ out->offset, out->size, slab->size);
+
+ size_t out_end = out->offset + out->size;
+ insert_region(slab, (struct vk_region) { reg.start, out->offset });
+ insert_region(slab, (struct vk_region) { out_end, reg.end });
+
+ slab->used += size;
+ return true;
+}
+
+bool vk_malloc_generic(struct mpvk_ctx *vk, VkMemoryRequirements reqs,
+ VkMemoryPropertyFlags flags, struct vk_memslice *out)
+{
+ struct vk_heap *heap = find_heap(vk, 0, flags, &reqs);
+ return slice_heap(vk, heap, reqs.size, reqs.alignment, out);
+}
+
+bool vk_malloc_buffer(struct mpvk_ctx *vk, VkBufferUsageFlags bufFlags,
+ VkMemoryPropertyFlags memFlags, VkDeviceSize size,
+ VkDeviceSize alignment, struct vk_bufslice *out)
+{
+ struct vk_heap *heap = find_heap(vk, bufFlags, memFlags, NULL);
+ if (!slice_heap(vk, heap, size, alignment, &out->mem))
+ return false;
+
+ struct vk_slab *slab = out->mem.priv;
+ out->buf = slab->buffer;
+ if (slab->data)
+ out->data = (void *)((uintptr_t)slab->data + (ptrdiff_t)out->mem.offset);
+
+ return true;
+}
diff --git a/video/out/vulkan/malloc.h b/video/out/vulkan/malloc.h
new file mode 100644
index 0000000..466c8d8
--- /dev/null
+++ b/video/out/vulkan/malloc.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "common.h"
+
+void vk_malloc_init(struct mpvk_ctx *vk);
+void vk_malloc_uninit(struct mpvk_ctx *vk);
+
+// Represents a single "slice" of generic (non-buffer) memory, plus some
+// metadata for accounting. This struct is essentially read-only.
+struct vk_memslice {
+ VkDeviceMemory vkmem;
+ size_t offset;
+ size_t size;
+ void *priv;
+};
+
+void vk_free_memslice(struct mpvk_ctx *vk, struct vk_memslice slice);
+bool vk_malloc_generic(struct mpvk_ctx *vk, VkMemoryRequirements reqs,
+ VkMemoryPropertyFlags flags, struct vk_memslice *out);
+
+// Represents a single "slice" of a larger buffer
+struct vk_bufslice {
+ struct vk_memslice mem; // must be freed by the user when done
+ VkBuffer buf; // the buffer this memory was sliced from
+ // For persistently mapped buffers, this points to the first usable byte of
+ // this slice.
+ void *data;
+};
+
+// Allocate a buffer slice. This is more efficient than vk_malloc_generic for
+// when the user needs lots of buffers, since it doesn't require
+// creating/destroying lots of (little) VkBuffers.
+bool vk_malloc_buffer(struct mpvk_ctx *vk, VkBufferUsageFlags bufFlags,
+ VkMemoryPropertyFlags memFlags, VkDeviceSize size,
+ VkDeviceSize alignment, struct vk_bufslice *out);
diff --git a/video/out/vulkan/ra_vk.c b/video/out/vulkan/ra_vk.c
new file mode 100644
index 0000000..f85e30e
--- /dev/null
+++ b/video/out/vulkan/ra_vk.c
@@ -0,0 +1,1747 @@
+#include "video/out/gpu/utils.h"
+#include "video/out/gpu/spirv.h"
+
+#include "ra_vk.h"
+#include "malloc.h"
+
+static struct ra_fns ra_fns_vk;
+
+// For ra.priv
+struct ra_vk {
+ struct mpvk_ctx *vk;
+ struct ra_tex *clear_tex; // stupid hack for clear()
+ struct vk_cmd *cmd; // currently recording cmd
+};
+
+struct mpvk_ctx *ra_vk_get(struct ra *ra)
+{
+ if (ra->fns != &ra_fns_vk)
+ return NULL;
+
+ struct ra_vk *p = ra->priv;
+ return p->vk;
+}
+
+// Returns a command buffer, or NULL on error
+static struct vk_cmd *vk_require_cmd(struct ra *ra)
+{
+ struct ra_vk *p = ra->priv;
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ if (!p->cmd)
+ p->cmd = vk_cmd_begin(vk, vk->pool);
+
+ return p->cmd;
+}
+
+// Note: This technically follows the flush() API, but we don't need
+// to expose that (and in fact, it's a bad idea) since we control flushing
+// behavior with ra_vk_present_frame already.
+static bool vk_flush(struct ra *ra, VkSemaphore *done)
+{
+ struct ra_vk *p = ra->priv;
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ if (p->cmd) {
+ if (!vk_cmd_submit(vk, p->cmd, done))
+ return false;
+ p->cmd = NULL;
+ }
+
+ return true;
+}
+
+// The callback's *priv will always be set to `ra`
+static void vk_callback(struct ra *ra, vk_cb callback, void *arg)
+{
+ struct ra_vk *p = ra->priv;
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ if (p->cmd) {
+ vk_cmd_callback(p->cmd, callback, ra, arg);
+ } else {
+ vk_dev_callback(vk, callback, ra, arg);
+ }
+}
+
+#define MAKE_LAZY_DESTRUCTOR(fun, argtype) \
+ static void fun##_lazy(struct ra *ra, argtype *arg) { \
+ vk_callback(ra, (vk_cb) fun, arg); \
+ }
+
+static void vk_destroy_ra(struct ra *ra)
+{
+ struct ra_vk *p = ra->priv;
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ vk_flush(ra, NULL);
+ mpvk_dev_wait_idle(vk);
+ ra_tex_free(ra, &p->clear_tex);
+
+ talloc_free(ra);
+}
+
+static bool vk_setup_formats(struct ra *ra)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ for (const struct vk_format *vk_fmt = vk_formats; vk_fmt->name; vk_fmt++) {
+ VkFormatProperties prop;
+ vkGetPhysicalDeviceFormatProperties(vk->physd, vk_fmt->iformat, &prop);
+
+ // As a bare minimum, we need to sample from an allocated image
+ VkFormatFeatureFlags flags = prop.optimalTilingFeatures;
+ if (!(flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT))
+ continue;
+
+ VkFormatFeatureFlags linear_bits, render_bits;
+ linear_bits = VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
+ render_bits = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
+ VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT;
+
+ struct ra_format *fmt = talloc_zero(ra, struct ra_format);
+ *fmt = (struct ra_format) {
+ .name = vk_fmt->name,
+ .priv = (void *)vk_fmt,
+ .ctype = vk_fmt->ctype,
+ .ordered = !vk_fmt->fucked_order,
+ .num_components = vk_fmt->components,
+ .pixel_size = vk_fmt->bytes,
+ .linear_filter = !!(flags & linear_bits),
+ .renderable = !!(flags & render_bits),
+ };
+
+ for (int i = 0; i < 4; i++)
+ fmt->component_size[i] = fmt->component_depth[i] = vk_fmt->bits[i];
+
+ fmt->glsl_format = ra_fmt_glsl_format(fmt);
+
+ MP_TARRAY_APPEND(ra, ra->formats, ra->num_formats, fmt);
+ }
+
+ // Populate some other capabilities related to formats while we're at it
+ VkImageType imgType[3] = {
+ VK_IMAGE_TYPE_1D,
+ VK_IMAGE_TYPE_2D,
+ VK_IMAGE_TYPE_3D
+ };
+
+ // R8_UNORM is supported on literally every single vulkan implementation
+ const VkFormat testfmt = VK_FORMAT_R8_UNORM;
+
+ for (int d = 0; d < 3; d++) {
+ VkImageFormatProperties iprop;
+ VkResult res = vkGetPhysicalDeviceImageFormatProperties(vk->physd,
+ testfmt, imgType[d], VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_SAMPLED_BIT, 0, &iprop);
+
+ switch (imgType[d]) {
+ case VK_IMAGE_TYPE_1D:
+ if (res == VK_SUCCESS)
+ ra->caps |= RA_CAP_TEX_1D;
+ break;
+ case VK_IMAGE_TYPE_2D:
+ // 2D formats must be supported by RA, so ensure this is the case
+ VK_ASSERT(res, "Querying 2D format limits");
+ ra->max_texture_wh = MPMIN(iprop.maxExtent.width, iprop.maxExtent.height);
+ break;
+ case VK_IMAGE_TYPE_3D:
+ if (res == VK_SUCCESS)
+ ra->caps |= RA_CAP_TEX_3D;
+ break;
+ }
+ }
+
+ // RA_CAP_BLIT implies both blitting between images as well as blitting
+ // directly to the swapchain image, so check for all three operations
+ bool blittable = true;
+ VkFormatProperties prop;
+ vkGetPhysicalDeviceFormatProperties(vk->physd, testfmt, &prop);
+ if (!(prop.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT))
+ blittable = false;
+ if (!(prop.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT))
+ blittable = false;
+
+ vkGetPhysicalDeviceFormatProperties(vk->physd, vk->surf_format.format, &prop);
+ if (!(prop.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT))
+ blittable = false;
+
+ if (blittable)
+ ra->caps |= RA_CAP_BLIT;
+
+ return true;
+
+error:
+ return false;
+}
+
+static struct ra_fns ra_fns_vk;
+
+struct ra *ra_create_vk(struct mpvk_ctx *vk, struct mp_log *log)
+{
+ assert(vk->dev);
+ assert(vk->alloc);
+
+ struct ra *ra = talloc_zero(NULL, struct ra);
+ ra->log = log;
+ ra->fns = &ra_fns_vk;
+
+ struct ra_vk *p = ra->priv = talloc_zero(ra, struct ra_vk);
+ p->vk = vk;
+
+ ra->caps |= vk->spirv->ra_caps;
+ ra->glsl_version = vk->spirv->glsl_version;
+ ra->glsl_vulkan = true;
+ ra->max_shmem = vk->limits.maxComputeSharedMemorySize;
+ ra->max_pushc_size = vk->limits.maxPushConstantsSize;
+
+ if (vk->pool->props.queueFlags & VK_QUEUE_COMPUTE_BIT)
+ ra->caps |= RA_CAP_COMPUTE;
+
+ if (!vk_setup_formats(ra))
+ goto error;
+
+ // UBO support is required
+ ra->caps |= RA_CAP_BUF_RO | RA_CAP_FRAGCOORD;
+
+ // textureGather is only supported in GLSL 400+
+ if (ra->glsl_version >= 400)
+ ra->caps |= RA_CAP_GATHER;
+
+ // Try creating a shader storage buffer
+ struct ra_buf_params ssbo_params = {
+ .type = RA_BUF_TYPE_SHADER_STORAGE,
+ .size = 16,
+ };
+
+ struct ra_buf *ssbo = ra_buf_create(ra, &ssbo_params);
+ if (ssbo) {
+ ra->caps |= RA_CAP_BUF_RW;
+ ra_buf_free(ra, &ssbo);
+ }
+
+ // To support clear() by region, we need to allocate a dummy 1x1 image that
+ // will be used as the source of blit operations
+ struct ra_tex_params clear_params = {
+ .dimensions = 1, // no point in using a 2D image if height = 1
+ .w = 1,
+ .h = 1,
+ .d = 1,
+ .format = ra_find_float16_format(ra, 4),
+ .blit_src = 1,
+ .host_mutable = 1,
+ };
+
+ p->clear_tex = ra_tex_create(ra, &clear_params);
+ if (!p->clear_tex) {
+ MP_ERR(ra, "Failed creating 1x1 dummy texture for clear()!\n");
+ goto error;
+ }
+
+ return ra;
+
+error:
+ vk_destroy_ra(ra);
+ return NULL;
+}
+
+// Boilerplate wrapper around vkCreateRenderPass to ensure passes remain
+// compatible
+static VkResult vk_create_render_pass(VkDevice dev, const struct ra_format *fmt,
+ bool load_fbo, VkRenderPass *out)
+{
+ struct vk_format *vk_fmt = fmt->priv;
+ assert(fmt->renderable);
+
+ VkRenderPassCreateInfo rinfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &(VkAttachmentDescription) {
+ .format = vk_fmt->iformat,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = load_fbo ? VK_ATTACHMENT_LOAD_OP_LOAD
+ : VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .initialLayout = load_fbo ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+ : VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ },
+ .subpassCount = 1,
+ .pSubpasses = &(VkSubpassDescription) {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &(VkAttachmentReference) {
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ },
+ },
+ };
+
+ return vkCreateRenderPass(dev, &rinfo, MPVK_ALLOCATOR, out);
+}
+
+// For ra_tex.priv
+struct ra_tex_vk {
+ bool external_img;
+ VkImageType type;
+ VkImage img;
+ struct vk_memslice mem;
+ // for sampling
+ VkImageView view;
+ VkSampler sampler;
+ // for rendering
+ VkFramebuffer framebuffer;
+ VkRenderPass dummyPass;
+ // for uploading
+ struct ra_buf_pool pbo;
+ // "current" metadata, can change during the course of execution
+ VkImageLayout current_layout;
+ VkPipelineStageFlags current_stage;
+ VkAccessFlags current_access;
+};
+
+// Small helper to ease image barrier creation. if `discard` is set, the contents
+// of the image will be undefined after the barrier
+static void tex_barrier(struct vk_cmd *cmd, struct ra_tex_vk *tex_vk,
+ VkPipelineStageFlags newStage, VkAccessFlags newAccess,
+ VkImageLayout newLayout, bool discard)
+{
+ VkImageMemoryBarrier imgBarrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = tex_vk->current_layout,
+ .newLayout = newLayout,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .srcAccessMask = tex_vk->current_access,
+ .dstAccessMask = newAccess,
+ .image = tex_vk->img,
+ .subresourceRange = vk_range,
+ };
+
+ if (discard) {
+ imgBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ imgBarrier.srcAccessMask = 0;
+ }
+
+ if (imgBarrier.oldLayout != imgBarrier.newLayout ||
+ imgBarrier.srcAccessMask != imgBarrier.dstAccessMask)
+ {
+ vkCmdPipelineBarrier(cmd->buf, tex_vk->current_stage, newStage, 0,
+ 0, NULL, 0, NULL, 1, &imgBarrier);
+ }
+
+ tex_vk->current_stage = newStage;
+ tex_vk->current_layout = newLayout;
+ tex_vk->current_access = newAccess;
+}
+
+static void vk_tex_destroy(struct ra *ra, struct ra_tex *tex)
+{
+ if (!tex)
+ return;
+
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct ra_tex_vk *tex_vk = tex->priv;
+
+ ra_buf_pool_uninit(ra, &tex_vk->pbo);
+ vkDestroyFramebuffer(vk->dev, tex_vk->framebuffer, MPVK_ALLOCATOR);
+ vkDestroyRenderPass(vk->dev, tex_vk->dummyPass, MPVK_ALLOCATOR);
+ vkDestroySampler(vk->dev, tex_vk->sampler, MPVK_ALLOCATOR);
+ vkDestroyImageView(vk->dev, tex_vk->view, MPVK_ALLOCATOR);
+ if (!tex_vk->external_img) {
+ vkDestroyImage(vk->dev, tex_vk->img, MPVK_ALLOCATOR);
+ vk_free_memslice(vk, tex_vk->mem);
+ }
+
+ talloc_free(tex);
+}
+
+MAKE_LAZY_DESTRUCTOR(vk_tex_destroy, struct ra_tex);
+
+// Initializes non-VkImage values like the image view, samplers, etc.
+static bool vk_init_image(struct ra *ra, struct ra_tex *tex)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ struct ra_tex_params *params = &tex->params;
+ struct ra_tex_vk *tex_vk = tex->priv;
+ assert(tex_vk->img);
+
+ tex_vk->current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ tex_vk->current_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ tex_vk->current_access = 0;
+
+ if (params->render_src || params->render_dst) {
+ static const VkImageViewType viewType[] = {
+ [VK_IMAGE_TYPE_1D] = VK_IMAGE_VIEW_TYPE_1D,
+ [VK_IMAGE_TYPE_2D] = VK_IMAGE_VIEW_TYPE_2D,
+ [VK_IMAGE_TYPE_3D] = VK_IMAGE_VIEW_TYPE_3D,
+ };
+
+ const struct vk_format *fmt = params->format->priv;
+ VkImageViewCreateInfo vinfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = tex_vk->img,
+ .viewType = viewType[tex_vk->type],
+ .format = fmt->iformat,
+ .subresourceRange = vk_range,
+ };
+
+ VK(vkCreateImageView(vk->dev, &vinfo, MPVK_ALLOCATOR, &tex_vk->view));
+ }
+
+ if (params->render_src) {
+ assert(params->format->linear_filter || !params->src_linear);
+ VkFilter filter = params->src_linear
+ ? VK_FILTER_LINEAR
+ : VK_FILTER_NEAREST;
+ VkSamplerAddressMode wrap = params->src_repeat
+ ? VK_SAMPLER_ADDRESS_MODE_REPEAT
+ : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ VkSamplerCreateInfo sinfo = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .magFilter = filter,
+ .minFilter = filter,
+ .addressModeU = wrap,
+ .addressModeV = wrap,
+ .addressModeW = wrap,
+ .maxAnisotropy = 1.0,
+ };
+
+ VK(vkCreateSampler(vk->dev, &sinfo, MPVK_ALLOCATOR, &tex_vk->sampler));
+ }
+
+ if (params->render_dst) {
+ // Framebuffers need to be created against a specific render pass
+ // layout, so we need to temporarily create a skeleton/dummy render
+ // pass for vulkan to figure out the compatibility
+ VK(vk_create_render_pass(vk->dev, params->format, false, &tex_vk->dummyPass));
+
+ VkFramebufferCreateInfo finfo = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = tex_vk->dummyPass,
+ .attachmentCount = 1,
+ .pAttachments = &tex_vk->view,
+ .width = tex->params.w,
+ .height = tex->params.h,
+ .layers = 1,
+ };
+
+ VK(vkCreateFramebuffer(vk->dev, &finfo, MPVK_ALLOCATOR,
+ &tex_vk->framebuffer));
+
+ // NOTE: Normally we would free the dummyPass again here, but a bug
+ // in the nvidia vulkan driver causes a segfault if you do.
+ }
+
+ return true;
+
+error:
+ return false;
+}
+
+static struct ra_tex *vk_tex_create(struct ra *ra,
+ const struct ra_tex_params *params)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ struct ra_tex *tex = talloc_zero(NULL, struct ra_tex);
+ tex->params = *params;
+ tex->params.initial_data = NULL;
+
+ struct ra_tex_vk *tex_vk = tex->priv = talloc_zero(tex, struct ra_tex_vk);
+
+ const struct vk_format *fmt = params->format->priv;
+ switch (params->dimensions) {
+ case 1: tex_vk->type = VK_IMAGE_TYPE_1D; break;
+ case 2: tex_vk->type = VK_IMAGE_TYPE_2D; break;
+ case 3: tex_vk->type = VK_IMAGE_TYPE_3D; break;
+ default: abort();
+ }
+
+ VkImageUsageFlags usage = 0;
+ if (params->render_src)
+ usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
+ if (params->render_dst)
+ usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ if (params->storage_dst)
+ usage |= VK_IMAGE_USAGE_STORAGE_BIT;
+ if (params->blit_src)
+ usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ if (params->host_mutable || params->blit_dst || params->initial_data)
+ usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+ // Double-check image usage support and fail immediately if invalid
+ VkImageFormatProperties iprop;
+ VkResult res = vkGetPhysicalDeviceImageFormatProperties(vk->physd,
+ fmt->iformat, tex_vk->type, VK_IMAGE_TILING_OPTIMAL, usage, 0,
+ &iprop);
+ if (res == VK_ERROR_FORMAT_NOT_SUPPORTED) {
+ return NULL;
+ } else {
+ VK_ASSERT(res, "Querying image format properties");
+ }
+
+ VkFormatProperties prop;
+ vkGetPhysicalDeviceFormatProperties(vk->physd, fmt->iformat, &prop);
+ VkFormatFeatureFlags flags = prop.optimalTilingFeatures;
+
+ bool has_blit_src = flags & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
+ has_src_linear = flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
+
+ if (params->w > iprop.maxExtent.width ||
+ params->h > iprop.maxExtent.height ||
+ params->d > iprop.maxExtent.depth ||
+ (params->blit_src && !has_blit_src) ||
+ (params->src_linear && !has_src_linear))
+ {
+ return NULL;
+ }
+
+ VkImageCreateInfo iinfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = tex_vk->type,
+ .format = fmt->iformat,
+ .extent = (VkExtent3D) { params->w, params->h, params->d },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = usage,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 1,
+ .pQueueFamilyIndices = &vk->pool->qf,
+ };
+
+ VK(vkCreateImage(vk->dev, &iinfo, MPVK_ALLOCATOR, &tex_vk->img));
+
+ VkMemoryPropertyFlags memFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ VkMemoryRequirements reqs;
+ vkGetImageMemoryRequirements(vk->dev, tex_vk->img, &reqs);
+
+ struct vk_memslice *mem = &tex_vk->mem;
+ if (!vk_malloc_generic(vk, reqs, memFlags, mem))
+ goto error;
+
+ VK(vkBindImageMemory(vk->dev, tex_vk->img, mem->vkmem, mem->offset));
+
+ if (!vk_init_image(ra, tex))
+ goto error;
+
+ if (params->initial_data) {
+ struct ra_tex_upload_params ul_params = {
+ .tex = tex,
+ .invalidate = true,
+ .src = params->initial_data,
+ .stride = params->w * fmt->bytes,
+ };
+ if (!ra->fns->tex_upload(ra, &ul_params))
+ goto error;
+ }
+
+ return tex;
+
+error:
+ vk_tex_destroy(ra, tex);
+ return NULL;
+}
+
+struct ra_tex *ra_vk_wrap_swapchain_img(struct ra *ra, VkImage vkimg,
+ VkSwapchainCreateInfoKHR info)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct ra_tex *tex = NULL;
+
+ const struct ra_format *format = NULL;
+ for (int i = 0; i < ra->num_formats; i++) {
+ const struct vk_format *fmt = ra->formats[i]->priv;
+ if (fmt->iformat == vk->surf_format.format) {
+ format = ra->formats[i];
+ break;
+ }
+ }
+
+ if (!format) {
+ MP_ERR(ra, "Could not find ra_format suitable for wrapped swchain image "
+ "with surface format 0x%x\n", vk->surf_format.format);
+ goto error;
+ }
+
+ tex = talloc_zero(NULL, struct ra_tex);
+ tex->params = (struct ra_tex_params) {
+ .format = format,
+ .dimensions = 2,
+ .w = info.imageExtent.width,
+ .h = info.imageExtent.height,
+ .d = 1,
+ .blit_src = !!(info.imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
+ .blit_dst = !!(info.imageUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT),
+ .render_src = !!(info.imageUsage & VK_IMAGE_USAGE_SAMPLED_BIT),
+ .render_dst = !!(info.imageUsage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
+ .storage_dst = !!(info.imageUsage & VK_IMAGE_USAGE_STORAGE_BIT),
+ };
+
+ struct ra_tex_vk *tex_vk = tex->priv = talloc_zero(tex, struct ra_tex_vk);
+ tex_vk->type = VK_IMAGE_TYPE_2D;
+ tex_vk->external_img = true;
+ tex_vk->img = vkimg;
+
+ if (!vk_init_image(ra, tex))
+ goto error;
+
+ return tex;
+
+error:
+ vk_tex_destroy(ra, tex);
+ return NULL;
+}
+
+// For ra_buf.priv
+struct ra_buf_vk {
+ struct vk_bufslice slice;
+ int refcount; // 1 = object allocated but not in use, > 1 = in use
+ bool needsflush;
+ // "current" metadata, can change during course of execution
+ VkPipelineStageFlags current_stage;
+ VkAccessFlags current_access;
+};
+
+static void vk_buf_deref(struct ra *ra, struct ra_buf *buf)
+{
+ if (!buf)
+ return;
+
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct ra_buf_vk *buf_vk = buf->priv;
+
+ if (--buf_vk->refcount == 0) {
+ vk_free_memslice(vk, buf_vk->slice.mem);
+ talloc_free(buf);
+ }
+}
+
+static void buf_barrier(struct ra *ra, struct vk_cmd *cmd, struct ra_buf *buf,
+ VkPipelineStageFlags newStage,
+ VkAccessFlags newAccess, int offset, size_t size)
+{
+ struct ra_buf_vk *buf_vk = buf->priv;
+
+ VkBufferMemoryBarrier buffBarrier = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+ .srcAccessMask = buf_vk->current_access,
+ .dstAccessMask = newAccess,
+ .buffer = buf_vk->slice.buf,
+ .offset = offset,
+ .size = size,
+ };
+
+ if (buf_vk->needsflush || buf->params.host_mapped) {
+ buffBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+ buf_vk->current_stage = VK_PIPELINE_STAGE_HOST_BIT;
+ buf_vk->needsflush = false;
+ }
+
+ if (buffBarrier.srcAccessMask != buffBarrier.dstAccessMask) {
+ vkCmdPipelineBarrier(cmd->buf, buf_vk->current_stage, newStage, 0,
+ 0, NULL, 1, &buffBarrier, 0, NULL);
+ }
+
+ buf_vk->current_stage = newStage;
+ buf_vk->current_access = newAccess;
+ buf_vk->refcount++;
+ vk_cmd_callback(cmd, (vk_cb) vk_buf_deref, ra, buf);
+}
+
+#define vk_buf_destroy vk_buf_deref
+MAKE_LAZY_DESTRUCTOR(vk_buf_destroy, struct ra_buf);
+
+static void vk_buf_update(struct ra *ra, struct ra_buf *buf, ptrdiff_t offset,
+ const void *data, size_t size)
+{
+ assert(buf->params.host_mutable || buf->params.initial_data);
+ struct ra_buf_vk *buf_vk = buf->priv;
+
+ // For host-mapped buffers, we can just directly memcpy the buffer contents.
+ // Otherwise, we can update the buffer from the GPU using a command buffer.
+ if (buf_vk->slice.data) {
+ assert(offset + size <= buf->params.size);
+ uintptr_t addr = (uintptr_t)buf_vk->slice.data + offset;
+ memcpy((void *)addr, data, size);
+ buf_vk->needsflush = true;
+ } else {
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd) {
+ MP_ERR(ra, "Failed updating buffer!\n");
+ return;
+ }
+
+ buf_barrier(ra, cmd, buf, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_ACCESS_TRANSFER_WRITE_BIT, offset, size);
+
+ VkDeviceSize bufOffset = buf_vk->slice.mem.offset + offset;
+ assert(bufOffset == MP_ALIGN_UP(bufOffset, 4));
+ vkCmdUpdateBuffer(cmd->buf, buf_vk->slice.buf, bufOffset, size, data);
+ }
+}
+
+static struct ra_buf *vk_buf_create(struct ra *ra,
+ const struct ra_buf_params *params)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ struct ra_buf *buf = talloc_zero(NULL, struct ra_buf);
+ buf->params = *params;
+
+ struct ra_buf_vk *buf_vk = buf->priv = talloc_zero(buf, struct ra_buf_vk);
+ buf_vk->current_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ buf_vk->current_access = 0;
+ buf_vk->refcount = 1;
+
+ VkBufferUsageFlags bufFlags = 0;
+ VkMemoryPropertyFlags memFlags = 0;
+ VkDeviceSize align = 4; // alignment 4 is needed for buf_update
+
+ switch (params->type) {
+ case RA_BUF_TYPE_TEX_UPLOAD:
+ bufFlags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+ memFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+ break;
+ case RA_BUF_TYPE_UNIFORM:
+ bufFlags |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+ memFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ align = MP_ALIGN_UP(align, vk->limits.minUniformBufferOffsetAlignment);
+ break;
+ case RA_BUF_TYPE_SHADER_STORAGE:
+ bufFlags |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+ memFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ align = MP_ALIGN_UP(align, vk->limits.minStorageBufferOffsetAlignment);
+ break;
+ case RA_BUF_TYPE_VERTEX:
+ bufFlags |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+ memFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ break;
+ default: abort();
+ }
+
+ if (params->host_mutable || params->initial_data) {
+ bufFlags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ align = MP_ALIGN_UP(align, vk->limits.optimalBufferCopyOffsetAlignment);
+ }
+
+ if (params->host_mapped) {
+ memFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
+ VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
+ }
+
+ if (!vk_malloc_buffer(vk, bufFlags, memFlags, params->size, align,
+ &buf_vk->slice))
+ {
+ goto error;
+ }
+
+ if (params->host_mapped)
+ buf->data = buf_vk->slice.data;
+
+ if (params->initial_data)
+ vk_buf_update(ra, buf, 0, params->initial_data, params->size);
+
+ buf->params.initial_data = NULL; // do this after vk_buf_update
+ return buf;
+
+error:
+ vk_buf_destroy(ra, buf);
+ return NULL;
+}
+
+static bool vk_buf_poll(struct ra *ra, struct ra_buf *buf)
+{
+ struct ra_buf_vk *buf_vk = buf->priv;
+ return buf_vk->refcount == 1;
+}
+
+static bool vk_tex_upload(struct ra *ra,
+ const struct ra_tex_upload_params *params)
+{
+ struct ra_tex *tex = params->tex;
+ struct ra_tex_vk *tex_vk = tex->priv;
+
+ if (!params->buf)
+ return ra_tex_upload_pbo(ra, &tex_vk->pbo, params);
+
+ assert(!params->src);
+ assert(params->buf);
+ struct ra_buf *buf = params->buf;
+ struct ra_buf_vk *buf_vk = buf->priv;
+
+ VkBufferImageCopy region = {
+ .bufferOffset = buf_vk->slice.mem.offset + params->buf_offset,
+ .bufferRowLength = tex->params.w,
+ .bufferImageHeight = tex->params.h,
+ .imageSubresource = vk_layers,
+ .imageExtent = (VkExtent3D){tex->params.w, tex->params.h, tex->params.d},
+ };
+
+ if (tex->params.dimensions == 2) {
+ int pix_size = tex->params.format->pixel_size;
+ region.bufferRowLength = params->stride / pix_size;
+ if (region.bufferRowLength * pix_size != params->stride) {
+ MP_ERR(ra, "Texture upload strides must be a multiple of the texel "
+ "size!\n");
+ goto error;
+ }
+
+ if (params->rc) {
+ struct mp_rect *rc = params->rc;
+ region.imageOffset = (VkOffset3D){rc->x0, rc->y0, 0};
+ region.imageExtent = (VkExtent3D){mp_rect_w(*rc), mp_rect_h(*rc), 1};
+ }
+ }
+
+ uint64_t size = region.bufferRowLength * region.bufferImageHeight *
+ region.imageExtent.depth;
+
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd)
+ goto error;
+
+ buf_barrier(ra, cmd, buf, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_ACCESS_TRANSFER_READ_BIT, region.bufferOffset, size);
+
+ tex_barrier(cmd, tex_vk, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ params->invalidate);
+
+ vkCmdCopyBufferToImage(cmd->buf, buf_vk->slice.buf, tex_vk->img,
+ tex_vk->current_layout, 1, &region);
+
+ return true;
+
+error:
+ return false;
+}
+
+#define MPVK_NUM_DS MPVK_MAX_STREAMING_DEPTH
+
+// For ra_renderpass.priv
+struct ra_renderpass_vk {
+ // Pipeline / render pass
+ VkPipeline pipe;
+ VkPipelineLayout pipeLayout;
+ VkRenderPass renderPass;
+ // Descriptor set (bindings)
+ VkDescriptorSetLayout dsLayout;
+ VkDescriptorPool dsPool;
+ VkDescriptorSet dss[MPVK_NUM_DS];
+ int dindex;
+ // Vertex buffers (vertices)
+ struct ra_buf_pool vbo;
+
+ // For updating
+ VkWriteDescriptorSet *dswrite;
+ VkDescriptorImageInfo *dsiinfo;
+ VkDescriptorBufferInfo *dsbinfo;
+};
+
+static void vk_renderpass_destroy(struct ra *ra, struct ra_renderpass *pass)
+{
+ if (!pass)
+ return;
+
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct ra_renderpass_vk *pass_vk = pass->priv;
+
+ ra_buf_pool_uninit(ra, &pass_vk->vbo);
+ vkDestroyPipeline(vk->dev, pass_vk->pipe, MPVK_ALLOCATOR);
+ vkDestroyRenderPass(vk->dev, pass_vk->renderPass, MPVK_ALLOCATOR);
+ vkDestroyPipelineLayout(vk->dev, pass_vk->pipeLayout, MPVK_ALLOCATOR);
+ vkDestroyDescriptorPool(vk->dev, pass_vk->dsPool, MPVK_ALLOCATOR);
+ vkDestroyDescriptorSetLayout(vk->dev, pass_vk->dsLayout, MPVK_ALLOCATOR);
+
+ talloc_free(pass);
+}
+
+MAKE_LAZY_DESTRUCTOR(vk_renderpass_destroy, struct ra_renderpass);
+
+static const VkDescriptorType dsType[] = {
+ [RA_VARTYPE_TEX] = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ [RA_VARTYPE_IMG_W] = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ [RA_VARTYPE_BUF_RO] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ [RA_VARTYPE_BUF_RW] = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+};
+
+static bool vk_get_input_format(struct ra *ra, struct ra_renderpass_input *inp,
+ VkFormat *out_fmt)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ enum ra_ctype ctype;
+ switch (inp->type) {
+ case RA_VARTYPE_FLOAT: ctype = RA_CTYPE_FLOAT; break;
+ case RA_VARTYPE_BYTE_UNORM: ctype = RA_CTYPE_UNORM; break;
+ default: abort();
+ }
+
+ assert(inp->dim_m == 1);
+ for (const struct vk_format *fmt = vk_formats; fmt->name; fmt++) {
+ if (fmt->ctype != ctype)
+ continue;
+ if (fmt->components != inp->dim_v)
+ continue;
+ if (fmt->bytes != ra_renderpass_input_layout(inp).size)
+ continue;
+
+ // Ensure this format is valid for vertex attributes
+ VkFormatProperties prop;
+ vkGetPhysicalDeviceFormatProperties(vk->physd, fmt->iformat, &prop);
+ if (!(prop.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT))
+ continue;
+
+ *out_fmt = fmt->iformat;
+ return true;
+ }
+
+ return false;
+}
+
+static const char vk_cache_magic[4] = {'R','A','V','K'};
+static const int vk_cache_version = 2;
+
+struct vk_cache_header {
+ char magic[sizeof(vk_cache_magic)];
+ int cache_version;
+ char compiler[SPIRV_NAME_MAX_LEN];
+ int compiler_version;
+ size_t vert_spirv_len;
+ size_t frag_spirv_len;
+ size_t comp_spirv_len;
+ size_t pipecache_len;
+};
+
+static bool vk_use_cached_program(const struct ra_renderpass_params *params,
+ const struct spirv_compiler *spirv,
+ struct bstr *vert_spirv,
+ struct bstr *frag_spirv,
+ struct bstr *comp_spirv,
+ struct bstr *pipecache)
+{
+ struct bstr cache = params->cached_program;
+ if (cache.len < sizeof(struct vk_cache_header))
+ return false;
+
+ struct vk_cache_header *header = (struct vk_cache_header *)cache.start;
+ cache = bstr_cut(cache, sizeof(*header));
+
+ if (strncmp(header->magic, vk_cache_magic, sizeof(vk_cache_magic)) != 0)
+ return false;
+ if (header->cache_version != vk_cache_version)
+ return false;
+ if (strncmp(header->compiler, spirv->name, sizeof(header->compiler)) != 0)
+ return false;
+ if (header->compiler_version != spirv->compiler_version)
+ return false;
+
+#define GET(ptr) \
+ if (cache.len < header->ptr##_len) \
+ return false; \
+ *ptr = bstr_splice(cache, 0, header->ptr##_len); \
+ cache = bstr_cut(cache, ptr->len);
+
+ GET(vert_spirv);
+ GET(frag_spirv);
+ GET(comp_spirv);
+ GET(pipecache);
+ return true;
+}
+
+static VkResult vk_compile_glsl(struct ra *ra, void *tactx,
+ enum glsl_shader type, const char *glsl,
+ struct bstr *spirv)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ VkResult ret = VK_SUCCESS;
+ int msgl = MSGL_DEBUG;
+
+ if (!vk->spirv->fns->compile_glsl(vk->spirv, tactx, type, glsl, spirv)) {
+ ret = VK_ERROR_INVALID_SHADER_NV;
+ msgl = MSGL_ERR;
+ }
+
+ static const char *shader_names[] = {
+ [GLSL_SHADER_VERTEX] = "vertex",
+ [GLSL_SHADER_FRAGMENT] = "fragment",
+ [GLSL_SHADER_COMPUTE] = "compute",
+ };
+
+ if (mp_msg_test(ra->log, msgl)) {
+ MP_MSG(ra, msgl, "%s shader source:\n", shader_names[type]);
+ mp_log_source(ra->log, msgl, glsl);
+ }
+ return ret;
+}
+
+static const VkShaderStageFlags stageFlags[] = {
+ [RA_RENDERPASS_TYPE_RASTER] = VK_SHADER_STAGE_FRAGMENT_BIT,
+ [RA_RENDERPASS_TYPE_COMPUTE] = VK_SHADER_STAGE_COMPUTE_BIT,
+};
+
+static struct ra_renderpass *vk_renderpass_create(struct ra *ra,
+ const struct ra_renderpass_params *params)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ bool success = false;
+ assert(vk->spirv);
+
+ struct ra_renderpass *pass = talloc_zero(NULL, struct ra_renderpass);
+ pass->params = *ra_renderpass_params_copy(pass, params);
+ pass->params.cached_program = (bstr){0};
+ struct ra_renderpass_vk *pass_vk = pass->priv =
+ talloc_zero(pass, struct ra_renderpass_vk);
+
+ // temporary allocations/objects
+ void *tmp = talloc_new(NULL);
+ VkPipelineCache pipeCache = NULL;
+ VkShaderModule vert_shader = NULL;
+ VkShaderModule frag_shader = NULL;
+ VkShaderModule comp_shader = NULL;
+
+ static int dsCount[RA_VARTYPE_COUNT] = {0};
+ VkDescriptorSetLayoutBinding *bindings = NULL;
+ int num_bindings = 0;
+
+ for (int i = 0; i < params->num_inputs; i++) {
+ struct ra_renderpass_input *inp = &params->inputs[i];
+ switch (inp->type) {
+ case RA_VARTYPE_TEX:
+ case RA_VARTYPE_IMG_W:
+ case RA_VARTYPE_BUF_RO:
+ case RA_VARTYPE_BUF_RW: {
+ VkDescriptorSetLayoutBinding desc = {
+ .binding = inp->binding,
+ .descriptorType = dsType[inp->type],
+ .descriptorCount = 1,
+ .stageFlags = stageFlags[params->type],
+ };
+
+ MP_TARRAY_APPEND(tmp, bindings, num_bindings, desc);
+ dsCount[inp->type]++;
+ break;
+ }
+ default: abort();
+ }
+ }
+
+ VkDescriptorPoolSize *dsPoolSizes = NULL;
+ int poolSizeCount = 0;
+
+ for (enum ra_vartype t = 0; t < RA_VARTYPE_COUNT; t++) {
+ if (dsCount[t] > 0) {
+ VkDescriptorPoolSize dssize = {
+ .type = dsType[t],
+ .descriptorCount = dsCount[t] * MPVK_NUM_DS,
+ };
+
+ MP_TARRAY_APPEND(tmp, dsPoolSizes, poolSizeCount, dssize);
+ }
+ }
+
+ VkDescriptorPoolCreateInfo pinfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .maxSets = MPVK_NUM_DS,
+ .pPoolSizes = dsPoolSizes,
+ .poolSizeCount = poolSizeCount,
+ };
+
+ VK(vkCreateDescriptorPool(vk->dev, &pinfo, MPVK_ALLOCATOR, &pass_vk->dsPool));
+
+ pass_vk->dswrite = talloc_array(pass, VkWriteDescriptorSet, num_bindings);
+ pass_vk->dsiinfo = talloc_array(pass, VkDescriptorImageInfo, num_bindings);
+ pass_vk->dsbinfo = talloc_array(pass, VkDescriptorBufferInfo, num_bindings);
+
+ VkDescriptorSetLayoutCreateInfo dinfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pBindings = bindings,
+ .bindingCount = num_bindings,
+ };
+
+ VK(vkCreateDescriptorSetLayout(vk->dev, &dinfo, MPVK_ALLOCATOR,
+ &pass_vk->dsLayout));
+
+ VkDescriptorSetLayout layouts[MPVK_NUM_DS];
+ for (int i = 0; i < MPVK_NUM_DS; i++)
+ layouts[i] = pass_vk->dsLayout;
+
+ VkDescriptorSetAllocateInfo ainfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .descriptorPool = pass_vk->dsPool,
+ .descriptorSetCount = MPVK_NUM_DS,
+ .pSetLayouts = layouts,
+ };
+
+ VK(vkAllocateDescriptorSets(vk->dev, &ainfo, pass_vk->dss));
+
+ VkPipelineLayoutCreateInfo linfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .setLayoutCount = 1,
+ .pSetLayouts = &pass_vk->dsLayout,
+ .pushConstantRangeCount = params->push_constants_size ? 1 : 0,
+ .pPushConstantRanges = &(VkPushConstantRange){
+ .stageFlags = stageFlags[params->type],
+ .offset = 0,
+ .size = params->push_constants_size,
+ },
+ };
+
+ VK(vkCreatePipelineLayout(vk->dev, &linfo, MPVK_ALLOCATOR,
+ &pass_vk->pipeLayout));
+
+ struct bstr vert = {0}, frag = {0}, comp = {0}, pipecache = {0};
+ if (vk_use_cached_program(params, vk->spirv, &vert, &frag, &comp, &pipecache)) {
+ MP_VERBOSE(ra, "Using cached SPIR-V and VkPipeline.\n");
+ } else {
+ pipecache.len = 0;
+ switch (params->type) {
+ case RA_RENDERPASS_TYPE_RASTER:
+ VK(vk_compile_glsl(ra, tmp, GLSL_SHADER_VERTEX,
+ params->vertex_shader, &vert));
+ VK(vk_compile_glsl(ra, tmp, GLSL_SHADER_FRAGMENT,
+ params->frag_shader, &frag));
+ comp.len = 0;
+ break;
+ case RA_RENDERPASS_TYPE_COMPUTE:
+ VK(vk_compile_glsl(ra, tmp, GLSL_SHADER_COMPUTE,
+ params->compute_shader, &comp));
+ frag.len = 0;
+ vert.len = 0;
+ break;
+ }
+ }
+
+ VkPipelineCacheCreateInfo pcinfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
+ .pInitialData = pipecache.start,
+ .initialDataSize = pipecache.len,
+ };
+
+ VK(vkCreatePipelineCache(vk->dev, &pcinfo, MPVK_ALLOCATOR, &pipeCache));
+
+ VkShaderModuleCreateInfo sinfo = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ };
+
+ switch (params->type) {
+ case RA_RENDERPASS_TYPE_RASTER: {
+ sinfo.pCode = (uint32_t *)vert.start;
+ sinfo.codeSize = vert.len;
+ VK(vkCreateShaderModule(vk->dev, &sinfo, MPVK_ALLOCATOR, &vert_shader));
+
+ sinfo.pCode = (uint32_t *)frag.start;
+ sinfo.codeSize = frag.len;
+ VK(vkCreateShaderModule(vk->dev, &sinfo, MPVK_ALLOCATOR, &frag_shader));
+
+ VkVertexInputAttributeDescription *attrs = talloc_array(tmp,
+ VkVertexInputAttributeDescription, params->num_vertex_attribs);
+
+ for (int i = 0; i < params->num_vertex_attribs; i++) {
+ struct ra_renderpass_input *inp = &params->vertex_attribs[i];
+ attrs[i] = (VkVertexInputAttributeDescription) {
+ .location = i,
+ .binding = 0,
+ .offset = inp->offset,
+ };
+
+ if (!vk_get_input_format(ra, inp, &attrs[i].format)) {
+ MP_ERR(ra, "No suitable VkFormat for vertex attrib '%s'!\n",
+ inp->name);
+ goto error;
+ }
+ }
+ VK(vk_create_render_pass(vk->dev, params->target_format,
+ params->enable_blend, &pass_vk->renderPass));
+
+ static const VkBlendFactor blendFactors[] = {
+ [RA_BLEND_ZERO] = VK_BLEND_FACTOR_ZERO,
+ [RA_BLEND_ONE] = VK_BLEND_FACTOR_ONE,
+ [RA_BLEND_SRC_ALPHA] = VK_BLEND_FACTOR_SRC_ALPHA,
+ [RA_BLEND_ONE_MINUS_SRC_ALPHA] = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ };
+
+ VkGraphicsPipelineCreateInfo cinfo = {
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .stageCount = 2,
+ .pStages = (VkPipelineShaderStageCreateInfo[]) {
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = vert_shader,
+ .pName = "main",
+ }, {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = frag_shader,
+ .pName = "main",
+ }
+ },
+ .pVertexInputState = &(VkPipelineVertexInputStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 1,
+ .pVertexBindingDescriptions = &(VkVertexInputBindingDescription) {
+ .binding = 0,
+ .stride = params->vertex_stride,
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
+ },
+ .vertexAttributeDescriptionCount = params->num_vertex_attribs,
+ .pVertexAttributeDescriptions = attrs,
+ },
+ .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ },
+ .pViewportState = &(VkPipelineViewportStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount = 1,
+ .scissorCount = 1,
+ },
+ .pRasterizationState = &(VkPipelineRasterizationStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode = VK_CULL_MODE_NONE,
+ .lineWidth = 1.0f,
+ },
+ .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ },
+ .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &(VkPipelineColorBlendAttachmentState) {
+ .blendEnable = params->enable_blend,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcColorBlendFactor = blendFactors[params->blend_src_rgb],
+ .dstColorBlendFactor = blendFactors[params->blend_dst_rgb],
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = blendFactors[params->blend_src_alpha],
+ .dstAlphaBlendFactor = blendFactors[params->blend_dst_alpha],
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
+ VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT |
+ VK_COLOR_COMPONENT_A_BIT,
+ },
+ },
+ .pDynamicState = &(VkPipelineDynamicStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .dynamicStateCount = 2,
+ .pDynamicStates = (VkDynamicState[]){
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ },
+ },
+ .layout = pass_vk->pipeLayout,
+ .renderPass = pass_vk->renderPass,
+ };
+
+ VK(vkCreateGraphicsPipelines(vk->dev, pipeCache, 1, &cinfo,
+ MPVK_ALLOCATOR, &pass_vk->pipe));
+ break;
+ }
+ case RA_RENDERPASS_TYPE_COMPUTE: {
+ sinfo.pCode = (uint32_t *)comp.start;
+ sinfo.codeSize = comp.len;
+ VK(vkCreateShaderModule(vk->dev, &sinfo, MPVK_ALLOCATOR, &comp_shader));
+
+ VkComputePipelineCreateInfo cinfo = {
+ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+ .stage = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_COMPUTE_BIT,
+ .module = comp_shader,
+ .pName = "main",
+ },
+ .layout = pass_vk->pipeLayout,
+ };
+
+ VK(vkCreateComputePipelines(vk->dev, pipeCache, 1, &cinfo,
+ MPVK_ALLOCATOR, &pass_vk->pipe));
+ break;
+ }
+ }
+
+ // Update params->cached_program
+ struct bstr cache = {0};
+ VK(vkGetPipelineCacheData(vk->dev, pipeCache, &cache.len, NULL));
+ cache.start = talloc_size(tmp, cache.len);
+ VK(vkGetPipelineCacheData(vk->dev, pipeCache, &cache.len, cache.start));
+
+ struct vk_cache_header header = {
+ .cache_version = vk_cache_version,
+ .compiler_version = vk->spirv->compiler_version,
+ .vert_spirv_len = vert.len,
+ .frag_spirv_len = frag.len,
+ .comp_spirv_len = comp.len,
+ .pipecache_len = cache.len,
+ };
+
+ for (int i = 0; i < MP_ARRAY_SIZE(header.magic); i++)
+ header.magic[i] = vk_cache_magic[i];
+ for (int i = 0; i < sizeof(vk->spirv->name); i++)
+ header.compiler[i] = vk->spirv->name[i];
+
+ struct bstr *prog = &pass->params.cached_program;
+ bstr_xappend(pass, prog, (struct bstr){ (char *) &header, sizeof(header) });
+ bstr_xappend(pass, prog, vert);
+ bstr_xappend(pass, prog, frag);
+ bstr_xappend(pass, prog, comp);
+ bstr_xappend(pass, prog, cache);
+
+ success = true;
+
+error:
+ if (!success) {
+ vk_renderpass_destroy(ra, pass);
+ pass = NULL;
+ }
+
+ vkDestroyShaderModule(vk->dev, vert_shader, MPVK_ALLOCATOR);
+ vkDestroyShaderModule(vk->dev, frag_shader, MPVK_ALLOCATOR);
+ vkDestroyShaderModule(vk->dev, comp_shader, MPVK_ALLOCATOR);
+ vkDestroyPipelineCache(vk->dev, pipeCache, MPVK_ALLOCATOR);
+ talloc_free(tmp);
+ return pass;
+}
+
+static void vk_update_descriptor(struct ra *ra, struct vk_cmd *cmd,
+ struct ra_renderpass *pass,
+ struct ra_renderpass_input_val val,
+ VkDescriptorSet ds, int idx)
+{
+ struct ra_renderpass_vk *pass_vk = pass->priv;
+ struct ra_renderpass_input *inp = &pass->params.inputs[val.index];
+
+ VkWriteDescriptorSet *wds = &pass_vk->dswrite[idx];
+ *wds = (VkWriteDescriptorSet) {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = ds,
+ .dstBinding = inp->binding,
+ .descriptorCount = 1,
+ .descriptorType = dsType[inp->type],
+ };
+
+ static const VkPipelineStageFlags passStages[] = {
+ [RA_RENDERPASS_TYPE_RASTER] = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ [RA_RENDERPASS_TYPE_COMPUTE] = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ };
+
+ switch (inp->type) {
+ case RA_VARTYPE_TEX: {
+ struct ra_tex *tex = *(struct ra_tex **)val.data;
+ struct ra_tex_vk *tex_vk = tex->priv;
+
+ assert(tex->params.render_src);
+ tex_barrier(cmd, tex_vk, passStages[pass->params.type],
+ VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false);
+
+ VkDescriptorImageInfo *iinfo = &pass_vk->dsiinfo[idx];
+ *iinfo = (VkDescriptorImageInfo) {
+ .sampler = tex_vk->sampler,
+ .imageView = tex_vk->view,
+ .imageLayout = tex_vk->current_layout,
+ };
+
+ wds->pImageInfo = iinfo;
+ break;
+ }
+ case RA_VARTYPE_IMG_W: {
+ struct ra_tex *tex = *(struct ra_tex **)val.data;
+ struct ra_tex_vk *tex_vk = tex->priv;
+
+ assert(tex->params.storage_dst);
+ tex_barrier(cmd, tex_vk, passStages[pass->params.type],
+ VK_ACCESS_SHADER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_GENERAL, false);
+
+ VkDescriptorImageInfo *iinfo = &pass_vk->dsiinfo[idx];
+ *iinfo = (VkDescriptorImageInfo) {
+ .imageView = tex_vk->view,
+ .imageLayout = tex_vk->current_layout,
+ };
+
+ wds->pImageInfo = iinfo;
+ break;
+ }
+ case RA_VARTYPE_BUF_RO:
+ case RA_VARTYPE_BUF_RW: {
+ struct ra_buf *buf = *(struct ra_buf **)val.data;
+ struct ra_buf_vk *buf_vk = buf->priv;
+
+ VkBufferUsageFlags access = VK_ACCESS_SHADER_READ_BIT;
+ if (inp->type == RA_VARTYPE_BUF_RW)
+ access |= VK_ACCESS_SHADER_WRITE_BIT;
+
+ buf_barrier(ra, cmd, buf, passStages[pass->params.type],
+ access, buf_vk->slice.mem.offset, buf->params.size);
+
+ VkDescriptorBufferInfo *binfo = &pass_vk->dsbinfo[idx];
+ *binfo = (VkDescriptorBufferInfo) {
+ .buffer = buf_vk->slice.buf,
+ .offset = buf_vk->slice.mem.offset,
+ .range = buf->params.size,
+ };
+
+ wds->pBufferInfo = binfo;
+ break;
+ }
+ }
+}
+
+static void vk_renderpass_run(struct ra *ra,
+ const struct ra_renderpass_run_params *params)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct ra_renderpass *pass = params->pass;
+ struct ra_renderpass_vk *pass_vk = pass->priv;
+
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd)
+ goto error;
+
+ static const VkPipelineBindPoint bindPoint[] = {
+ [RA_RENDERPASS_TYPE_RASTER] = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ [RA_RENDERPASS_TYPE_COMPUTE] = VK_PIPELINE_BIND_POINT_COMPUTE,
+ };
+
+ vkCmdBindPipeline(cmd->buf, bindPoint[pass->params.type], pass_vk->pipe);
+
+ VkDescriptorSet ds = pass_vk->dss[pass_vk->dindex++];
+ pass_vk->dindex %= MPVK_NUM_DS;
+
+ for (int i = 0; i < params->num_values; i++)
+ vk_update_descriptor(ra, cmd, pass, params->values[i], ds, i);
+
+ if (params->num_values > 0) {
+ vkUpdateDescriptorSets(vk->dev, params->num_values, pass_vk->dswrite,
+ 0, NULL);
+ }
+
+ vkCmdBindDescriptorSets(cmd->buf, bindPoint[pass->params.type],
+ pass_vk->pipeLayout, 0, 1, &ds, 0, NULL);
+
+ if (pass->params.push_constants_size) {
+ vkCmdPushConstants(cmd->buf, pass_vk->pipeLayout,
+ stageFlags[pass->params.type], 0,
+ pass->params.push_constants_size,
+ params->push_constants);
+ }
+
+ switch (pass->params.type) {
+ case RA_RENDERPASS_TYPE_COMPUTE:
+ vkCmdDispatch(cmd->buf, params->compute_groups[0],
+ params->compute_groups[1],
+ params->compute_groups[2]);
+ break;
+ case RA_RENDERPASS_TYPE_RASTER: {
+ struct ra_tex *tex = params->target;
+ struct ra_tex_vk *tex_vk = tex->priv;
+ assert(tex->params.render_dst);
+
+ struct ra_buf_params buf_params = {
+ .type = RA_BUF_TYPE_VERTEX,
+ .size = params->vertex_count * pass->params.vertex_stride,
+ .host_mutable = true,
+ };
+
+ struct ra_buf *buf = ra_buf_pool_get(ra, &pass_vk->vbo, &buf_params);
+ if (!buf) {
+ MP_ERR(ra, "Failed allocating vertex buffer!\n");
+ goto error;
+ }
+ struct ra_buf_vk *buf_vk = buf->priv;
+
+ vk_buf_update(ra, buf, 0, params->vertex_data, buf_params.size);
+
+ buf_barrier(ra, cmd, buf, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+ VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+ buf_vk->slice.mem.offset, buf_params.size);
+
+ vkCmdBindVertexBuffers(cmd->buf, 0, 1, &buf_vk->slice.buf,
+ &buf_vk->slice.mem.offset);
+
+ if (pass->params.enable_blend) {
+ // Normally this transition is handled implicitly by the renderpass,
+ // but if we need to preserve the FBO we have to do it manually.
+ tex_barrier(cmd, tex_vk, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, false);
+ }
+
+ VkViewport viewport = {
+ .x = params->viewport.x0,
+ .y = params->viewport.y0,
+ .width = mp_rect_w(params->viewport),
+ .height = mp_rect_h(params->viewport),
+ };
+
+ VkRect2D scissor = {
+ .offset = {params->scissors.x0, params->scissors.y0},
+ .extent = {mp_rect_w(params->scissors), mp_rect_h(params->scissors)},
+ };
+
+ vkCmdSetViewport(cmd->buf, 0, 1, &viewport);
+ vkCmdSetScissor(cmd->buf, 0, 1, &scissor);
+
+ VkRenderPassBeginInfo binfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = pass_vk->renderPass,
+ .framebuffer = tex_vk->framebuffer,
+ .renderArea = (VkRect2D){{0, 0}, {tex->params.w, tex->params.h}},
+ };
+
+ vkCmdBeginRenderPass(cmd->buf, &binfo, VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdDraw(cmd->buf, params->vertex_count, 1, 0, 0);
+ vkCmdEndRenderPass(cmd->buf);
+
+ // The renderPass implicitly transitions the texture to this layout
+ tex_vk->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ tex_vk->current_access = VK_ACCESS_SHADER_READ_BIT;
+ tex_vk->current_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ break;
+ }
+ default: abort();
+ };
+
+error:
+ return;
+}
+
+static void vk_blit(struct ra *ra, struct ra_tex *dst, struct ra_tex *src,
+ struct mp_rect *dst_rc, struct mp_rect *src_rc)
+{
+ assert(src->params.blit_src);
+ assert(dst->params.blit_dst);
+
+ struct ra_tex_vk *src_vk = src->priv;
+ struct ra_tex_vk *dst_vk = dst->priv;
+
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd)
+ return;
+
+ tex_barrier(cmd, src_vk, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_ACCESS_TRANSFER_READ_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ false);
+
+ bool discard = dst_rc->x0 == 0 &&
+ dst_rc->y0 == 0 &&
+ dst_rc->x1 == dst->params.w &&
+ dst_rc->y1 == dst->params.h;
+
+ tex_barrier(cmd, dst_vk, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ discard);
+
+ VkImageBlit region = {
+ .srcSubresource = vk_layers,
+ .srcOffsets = {{src_rc->x0, src_rc->y0, 0}, {src_rc->x1, src_rc->y1, 1}},
+ .dstSubresource = vk_layers,
+ .dstOffsets = {{dst_rc->x0, dst_rc->y0, 0}, {dst_rc->x1, dst_rc->y1, 1}},
+ };
+
+ vkCmdBlitImage(cmd->buf, src_vk->img, src_vk->current_layout, dst_vk->img,
+ dst_vk->current_layout, 1, &region, VK_FILTER_NEAREST);
+}
+
+static void vk_clear(struct ra *ra, struct ra_tex *tex, float color[4],
+ struct mp_rect *rc)
+{
+ struct ra_vk *p = ra->priv;
+ struct ra_tex_vk *tex_vk = tex->priv;
+ assert(tex->params.blit_dst);
+
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd)
+ return;
+
+ struct mp_rect full = {0, 0, tex->params.w, tex->params.h};
+ if (!rc || mp_rect_equals(rc, &full)) {
+ // To clear the entire image, we can use the efficient clear command
+ tex_barrier(cmd, tex_vk, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true);
+
+ VkClearColorValue clearColor = {0};
+ for (int c = 0; c < 4; c++)
+ clearColor.float32[c] = color[c];
+
+ vkCmdClearColorImage(cmd->buf, tex_vk->img, tex_vk->current_layout,
+ &clearColor, 1, &vk_range);
+ } else {
+ // To simulate per-region clearing, we blit from a 1x1 texture instead
+ struct ra_tex_upload_params ul_params = {
+ .tex = p->clear_tex,
+ .invalidate = true,
+ .src = &color[0],
+ };
+ vk_tex_upload(ra, &ul_params);
+ vk_blit(ra, tex, p->clear_tex, rc, &(struct mp_rect){0, 0, 1, 1});
+ }
+}
+
+static int vk_desc_namespace(enum ra_vartype type)
+{
+ return 0;
+}
+
+#define VK_QUERY_POOL_SIZE (MPVK_MAX_STREAMING_DEPTH * 4)
+
+struct vk_timer {
+ VkQueryPool pool;
+ int index;
+ uint64_t result;
+};
+
+static void vk_timer_destroy(struct ra *ra, ra_timer *ratimer)
+{
+ if (!ratimer)
+ return;
+
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct vk_timer *timer = ratimer;
+
+ vkDestroyQueryPool(vk->dev, timer->pool, MPVK_ALLOCATOR);
+
+ talloc_free(timer);
+}
+
+MAKE_LAZY_DESTRUCTOR(vk_timer_destroy, ra_timer);
+
+static ra_timer *vk_timer_create(struct ra *ra)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+
+ struct vk_timer *timer = talloc_zero(NULL, struct vk_timer);
+
+ struct VkQueryPoolCreateInfo qinfo = {
+ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
+ .queryType = VK_QUERY_TYPE_TIMESTAMP,
+ .queryCount = VK_QUERY_POOL_SIZE,
+ };
+
+ VK(vkCreateQueryPool(vk->dev, &qinfo, MPVK_ALLOCATOR, &timer->pool));
+
+ return (ra_timer *)timer;
+
+error:
+ vk_timer_destroy(ra, timer);
+ return NULL;
+}
+
+static void vk_timer_record(struct ra *ra, VkQueryPool pool, int index,
+ VkPipelineStageFlags stage)
+{
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd)
+ return;
+
+ vkCmdWriteTimestamp(cmd->buf, stage, pool, index);
+}
+
+static void vk_timer_start(struct ra *ra, ra_timer *ratimer)
+{
+ struct mpvk_ctx *vk = ra_vk_get(ra);
+ struct vk_timer *timer = ratimer;
+
+ timer->index = (timer->index + 2) % VK_QUERY_POOL_SIZE;
+
+ uint64_t out[2];
+ VkResult res = vkGetQueryPoolResults(vk->dev, timer->pool, timer->index, 2,
+ sizeof(out), &out[0], sizeof(uint64_t),
+ VK_QUERY_RESULT_64_BIT);
+ switch (res) {
+ case VK_SUCCESS:
+ timer->result = (out[1] - out[0]) * vk->limits.timestampPeriod;
+ break;
+ case VK_NOT_READY:
+ timer->result = 0;
+ break;
+ default:
+ MP_WARN(vk, "Failed reading timer query result: %s\n", vk_err(res));
+ return;
+ };
+
+ vk_timer_record(ra, timer->pool, timer->index,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
+}
+
+static uint64_t vk_timer_stop(struct ra *ra, ra_timer *ratimer)
+{
+ struct vk_timer *timer = ratimer;
+ vk_timer_record(ra, timer->pool, timer->index + 1,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
+
+ return timer->result;
+}
+
+static struct ra_fns ra_fns_vk = {
+ .destroy = vk_destroy_ra,
+ .tex_create = vk_tex_create,
+ .tex_destroy = vk_tex_destroy_lazy,
+ .tex_upload = vk_tex_upload,
+ .buf_create = vk_buf_create,
+ .buf_destroy = vk_buf_destroy_lazy,
+ .buf_update = vk_buf_update,
+ .buf_poll = vk_buf_poll,
+ .clear = vk_clear,
+ .blit = vk_blit,
+ .uniform_layout = std140_layout,
+ .push_constant_layout = std430_layout,
+ .desc_namespace = vk_desc_namespace,
+ .renderpass_create = vk_renderpass_create,
+ .renderpass_destroy = vk_renderpass_destroy_lazy,
+ .renderpass_run = vk_renderpass_run,
+ .timer_create = vk_timer_create,
+ .timer_destroy = vk_timer_destroy_lazy,
+ .timer_start = vk_timer_start,
+ .timer_stop = vk_timer_stop,
+};
+
+static void present_cb(void *priv, int *inflight)
+{
+ *inflight -= 1;
+}
+
+bool ra_vk_submit(struct ra *ra, struct ra_tex *tex, VkSemaphore acquired,
+ VkSemaphore *done, int *inflight)
+{
+ struct vk_cmd *cmd = vk_require_cmd(ra);
+ if (!cmd)
+ goto error;
+
+ if (inflight) {
+ *inflight += 1;
+ vk_cmd_callback(cmd, (vk_cb)present_cb, NULL, inflight);
+ }
+
+ struct ra_tex_vk *tex_vk = tex->priv;
+ assert(tex_vk->external_img);
+ tex_barrier(cmd, tex_vk, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
+ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, false);
+
+ // These are the only two stages that we use/support for actually
+ // outputting to swapchain imagechain images, so just add a dependency
+ // on both of them. In theory, we could maybe come up with some more
+ // advanced mechanism of tracking dynamic dependencies, but that seems
+ // like overkill.
+ vk_cmd_dep(cmd, acquired,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
+ VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ return vk_flush(ra, done);
+
+error:
+ return false;
+}
diff --git a/video/out/vulkan/ra_vk.h b/video/out/vulkan/ra_vk.h
new file mode 100644
index 0000000..893421b
--- /dev/null
+++ b/video/out/vulkan/ra_vk.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "video/out/gpu/ra.h"
+
+#include "common.h"
+#include "utils.h"
+
+struct ra *ra_create_vk(struct mpvk_ctx *vk, struct mp_log *log);
+
+// Access to the VkDevice is needed for swapchain creation
+VkDevice ra_vk_get_dev(struct ra *ra);
+
+// Allocates a ra_tex that wraps a swapchain image. The contents of the image
+// will be invalidated, and access to it will only be internally synchronized.
+// So the calling could should not do anything else with the VkImage.
+struct ra_tex *ra_vk_wrap_swapchain_img(struct ra *ra, VkImage vkimg,
+ VkSwapchainCreateInfoKHR info);
+
+// This function flushes the command buffers, transitions `tex` (which must be
+// a wrapped swapchain image) into a format suitable for presentation, and
+// submits the current rendering commands. The indicated semaphore must fire
+// before the submitted command can run. If `done` is non-NULL, it will be
+// set to a semaphore that fires once the command completes. If `inflight`
+// is non-NULL, it will be incremented when the command starts and decremented
+// when it completes.
+bool ra_vk_submit(struct ra *ra, struct ra_tex *tex, VkSemaphore acquired,
+ VkSemaphore *done, int *inflight);
+
+// May be called on a struct ra of any type. Returns NULL if the ra is not
+// a vulkan ra.
+struct mpvk_ctx *ra_vk_get(struct ra *ra);
diff --git a/video/out/vulkan/spirv_nvidia.c b/video/out/vulkan/spirv_nvidia.c
new file mode 100644
index 0000000..6cc43a5
--- /dev/null
+++ b/video/out/vulkan/spirv_nvidia.c
@@ -0,0 +1,54 @@
+#include "video/out/gpu/spirv.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+static bool nv_glsl_compile(struct spirv_compiler *spirv, void *tactx,
+ enum glsl_shader type, const char *glsl,
+ struct bstr *out_spirv)
+{
+ // The nvidia extension literally assumes your SPIRV is in fact valid GLSL
+ *out_spirv = bstr0(glsl);
+ return true;
+}
+
+static bool nv_glsl_init(struct ra_ctx *ctx)
+{
+ struct mpvk_ctx *vk = ra_vk_ctx_get(ctx);
+ if (!vk)
+ return false;
+
+ struct spirv_compiler *spv = ctx->spirv;
+ spv->required_ext = VK_NV_GLSL_SHADER_EXTENSION_NAME;
+ spv->glsl_version = 450; // impossible to query, so hard-code it..
+ spv->ra_caps = RA_CAP_NESTED_ARRAY;
+
+ // Make sure the extension is actually available, and fail gracefully
+ // if it isn't
+ VkExtensionProperties *props = NULL;
+ uint32_t extnum = 0;
+ VK(vkEnumerateDeviceExtensionProperties(vk->physd, NULL, &extnum, NULL));
+ props = talloc_array(NULL, VkExtensionProperties, extnum);
+ VK(vkEnumerateDeviceExtensionProperties(vk->physd, NULL, &extnum, props));
+
+ bool ret = true;
+ for (int e = 0; e < extnum; e++) {
+ if (strncmp(props[e].extensionName, spv->required_ext,
+ VK_MAX_EXTENSION_NAME_SIZE) == 0)
+ goto done;
+ }
+
+error:
+ MP_VERBOSE(ctx, "Device doesn't support VK_NV_glsl_shader, skipping..\n");
+ ret = false;
+
+done:
+ talloc_free(props);
+ return ret;
+}
+
+const struct spirv_compiler_fns spirv_nvidia_builtin = {
+ .compile_glsl = nv_glsl_compile,
+ .init = nv_glsl_init,
+};
diff --git a/video/out/vulkan/utils.c b/video/out/vulkan/utils.c
new file mode 100644
index 0000000..baf0ebc
--- /dev/null
+++ b/video/out/vulkan/utils.c
@@ -0,0 +1,729 @@
+#include <libavutil/macros.h>
+
+#include "video/out/gpu/spirv.h"
+#include "utils.h"
+#include "malloc.h"
+
+const char* vk_err(VkResult res)
+{
+ switch (res) {
+ // These are technically success codes, but include them nonetheless
+ case VK_SUCCESS: return "VK_SUCCESS";
+ case VK_NOT_READY: return "VK_NOT_READY";
+ case VK_TIMEOUT: return "VK_TIMEOUT";
+ case VK_EVENT_SET: return "VK_EVENT_SET";
+ case VK_EVENT_RESET: return "VK_EVENT_RESET";
+ case VK_INCOMPLETE: return "VK_INCOMPLETE";
+ case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR";
+
+ // Actual error codes
+ case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY";
+ case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
+ case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED";
+ case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST";
+ case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
+ case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
+ case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT";
+ case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT";
+ case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER";
+ case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS";
+ case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED";
+ case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL";
+ case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV";
+ case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR";
+ case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR";
+ }
+
+ return "Unknown error!";
+}
+
+static const char* vk_dbg_type(VkDebugReportObjectTypeEXT type)
+{
+ switch (type) {
+ case VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT:
+ return "VkInstance";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT:
+ return "VkPhysicalDevice";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT:
+ return "VkDevice";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT:
+ return "VkQueue";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT:
+ return "VkSemaphore";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT:
+ return "VkCommandBuffer";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT:
+ return "VkFence";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT:
+ return "VkDeviceMemory";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT:
+ return "VkBuffer";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT:
+ return "VkImage";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT:
+ return "VkEvent";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT:
+ return "VkQueryPool";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT:
+ return "VkBufferView";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT:
+ return "VkImageView";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT:
+ return "VkShaderModule";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT:
+ return "VkPipelineCache";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT:
+ return "VkPipelineLayout";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT:
+ return "VkRenderPass";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT:
+ return "VkPipeline";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT:
+ return "VkDescriptorSetLayout";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT:
+ return "VkSampler";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT:
+ return "VkDescriptorPool";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT:
+ return "VkDescriptorSet";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT:
+ return "VkFramebuffer";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT:
+ return "VkCommandPool";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT:
+ return "VkSurfaceKHR";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT:
+ return "VkSwapchainKHR";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT:
+ return "VkDebugReportCallbackEXT";
+ case VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT:
+ default:
+ return "unknown object";
+ }
+}
+
+static VkBool32 vk_dbg_callback(VkDebugReportFlagsEXT flags,
+ VkDebugReportObjectTypeEXT objType,
+ uint64_t obj, size_t loc, int32_t msgCode,
+ const char *layer, const char *msg, void *priv)
+{
+ struct mpvk_ctx *vk = priv;
+ int lev = MSGL_V;
+
+ switch (flags) {
+ case VK_DEBUG_REPORT_ERROR_BIT_EXT: lev = MSGL_ERR; break;
+ case VK_DEBUG_REPORT_WARNING_BIT_EXT: lev = MSGL_WARN; break;
+ case VK_DEBUG_REPORT_INFORMATION_BIT_EXT: lev = MSGL_TRACE; break;
+ case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT: lev = MSGL_WARN; break;
+ case VK_DEBUG_REPORT_DEBUG_BIT_EXT: lev = MSGL_DEBUG; break;
+ };
+
+ MP_MSG(vk, lev, "vk [%s] %d: %s (obj 0x%llx (%s), loc 0x%zx)\n",
+ layer, (int)msgCode, msg, (unsigned long long)obj,
+ vk_dbg_type(objType), loc);
+
+ // The return value of this function determines whether the call will
+ // be explicitly aborted (to prevent GPU errors) or not. In this case,
+ // we generally want this to be on for the errors.
+ return (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT);
+}
+
+static void vk_cmdpool_uninit(struct mpvk_ctx *vk, struct vk_cmdpool *pool)
+{
+ if (!pool)
+ return;
+
+ // also frees associated command buffers
+ vkDestroyCommandPool(vk->dev, pool->pool, MPVK_ALLOCATOR);
+ for (int n = 0; n < MPVK_MAX_CMDS; n++) {
+ vkDestroyFence(vk->dev, pool->cmds[n].fence, MPVK_ALLOCATOR);
+ vkDestroySemaphore(vk->dev, pool->cmds[n].done, MPVK_ALLOCATOR);
+ talloc_free(pool->cmds[n].callbacks);
+ }
+ talloc_free(pool);
+}
+
+void mpvk_uninit(struct mpvk_ctx *vk)
+{
+ if (!vk->inst)
+ return;
+
+ if (vk->dev) {
+ vk_cmdpool_uninit(vk, vk->pool);
+ vk_malloc_uninit(vk);
+ vkDestroyDevice(vk->dev, MPVK_ALLOCATOR);
+ }
+
+ if (vk->dbg) {
+ // Same deal as creating the debug callback, we need to load this
+ // first.
+ VK_LOAD_PFN(vkDestroyDebugReportCallbackEXT)
+ pfn_vkDestroyDebugReportCallbackEXT(vk->inst, vk->dbg, MPVK_ALLOCATOR);
+ }
+
+ vkDestroySurfaceKHR(vk->inst, vk->surf, MPVK_ALLOCATOR);
+ vkDestroyInstance(vk->inst, MPVK_ALLOCATOR);
+
+ *vk = (struct mpvk_ctx){0};
+}
+
+bool mpvk_instance_init(struct mpvk_ctx *vk, struct mp_log *log,
+ const char *surf_ext_name, bool debug)
+{
+ *vk = (struct mpvk_ctx) {
+ .log = log,
+ };
+
+ VkInstanceCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ };
+
+ if (debug) {
+ // Enables the LunarG standard validation layer, which
+ // is a meta-layer that loads lots of other validators
+ static const char* layers[] = {
+ "VK_LAYER_LUNARG_standard_validation",
+ };
+
+ info.ppEnabledLayerNames = layers;
+ info.enabledLayerCount = MP_ARRAY_SIZE(layers);
+ }
+
+ // Enable whatever extensions were compiled in.
+ const char *extensions[] = {
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ surf_ext_name,
+
+ // Extra extensions only used for debugging. These are toggled by
+ // decreasing the enabledExtensionCount, so the number needs to be
+ // synchronized with the code below.
+ VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
+ };
+
+ const int debugExtensionCount = 1;
+
+ info.ppEnabledExtensionNames = extensions;
+ info.enabledExtensionCount = MP_ARRAY_SIZE(extensions);
+
+ if (!debug)
+ info.enabledExtensionCount -= debugExtensionCount;
+
+ MP_VERBOSE(vk, "Creating instance with extensions:\n");
+ for (int i = 0; i < info.enabledExtensionCount; i++)
+ MP_VERBOSE(vk, " %s\n", info.ppEnabledExtensionNames[i]);
+
+ VkResult res = vkCreateInstance(&info, MPVK_ALLOCATOR, &vk->inst);
+ if (res != VK_SUCCESS) {
+ MP_VERBOSE(vk, "Failed creating instance: %s\n", vk_err(res));
+ return false;
+ }
+
+ if (debug) {
+ // Set up a debug callback to catch validation messages
+ VkDebugReportCallbackCreateInfoEXT dinfo = {
+ .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
+ .flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
+ VK_DEBUG_REPORT_WARNING_BIT_EXT |
+ VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
+ VK_DEBUG_REPORT_ERROR_BIT_EXT |
+ VK_DEBUG_REPORT_DEBUG_BIT_EXT,
+ .pfnCallback = vk_dbg_callback,
+ .pUserData = vk,
+ };
+
+ // Since this is not part of the core spec, we need to load it. This
+ // can't fail because we've already successfully created an instance
+ // with this extension enabled.
+ VK_LOAD_PFN(vkCreateDebugReportCallbackEXT)
+ pfn_vkCreateDebugReportCallbackEXT(vk->inst, &dinfo, MPVK_ALLOCATOR,
+ &vk->dbg);
+ }
+
+ return true;
+}
+
+#define MPVK_MAX_DEVICES 16
+
+static bool physd_supports_surface(struct mpvk_ctx *vk, VkPhysicalDevice physd)
+{
+ uint32_t qfnum;
+ vkGetPhysicalDeviceQueueFamilyProperties(physd, &qfnum, NULL);
+
+ for (int i = 0; i < qfnum; i++) {
+ VkBool32 sup;
+ VK(vkGetPhysicalDeviceSurfaceSupportKHR(physd, i, vk->surf, &sup));
+ if (sup)
+ return true;
+ }
+
+error:
+ return false;
+}
+
+bool mpvk_find_phys_device(struct mpvk_ctx *vk, const char *name, bool sw)
+{
+ assert(vk->surf);
+
+ MP_VERBOSE(vk, "Probing for vulkan devices:\n");
+
+ VkPhysicalDevice *devices = NULL;
+ uint32_t num = 0;
+ VK(vkEnumeratePhysicalDevices(vk->inst, &num, NULL));
+ devices = talloc_array(NULL, VkPhysicalDevice, num);
+ VK(vkEnumeratePhysicalDevices(vk->inst, &num, devices));
+
+ // Sorted by "priority". Reuses some m_opt code for convenience
+ static const struct m_opt_choice_alternatives types[] = {
+ {"discrete", VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU},
+ {"integrated", VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU},
+ {"virtual", VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU},
+ {"software", VK_PHYSICAL_DEVICE_TYPE_CPU},
+ {"unknown", VK_PHYSICAL_DEVICE_TYPE_OTHER},
+ {0}
+ };
+
+ VkPhysicalDeviceProperties props[MPVK_MAX_DEVICES];
+ for (int i = 0; i < num; i++) {
+ vkGetPhysicalDeviceProperties(devices[i], &props[i]);
+ MP_VERBOSE(vk, " GPU %d: %s (%s)\n", i, props[i].deviceName,
+ m_opt_choice_str(types, props[i].deviceType));
+ }
+
+ // Iterate through each type in order of decreasing preference
+ for (int t = 0; types[t].name; t++) {
+ // Disallow SW rendering unless explicitly enabled
+ if (types[t].value == VK_PHYSICAL_DEVICE_TYPE_CPU && !sw)
+ continue;
+
+ for (int i = 0; i < num; i++) {
+ VkPhysicalDeviceProperties prop = props[i];
+ if (prop.deviceType != types[t].value)
+ continue;
+ if (name && strcmp(name, prop.deviceName) != 0)
+ continue;
+ if (!physd_supports_surface(vk, devices[i]))
+ continue;
+
+ MP_VERBOSE(vk, "Chose device:\n");
+ MP_VERBOSE(vk, " Device Name: %s\n", prop.deviceName);
+ MP_VERBOSE(vk, " Device ID: %x:%x\n",
+ (unsigned)prop.vendorID, (unsigned)prop.deviceID);
+ MP_VERBOSE(vk, " Driver version: %d\n", (int)prop.driverVersion);
+ MP_VERBOSE(vk, " API version: %d.%d.%d\n",
+ (int)VK_VERSION_MAJOR(prop.apiVersion),
+ (int)VK_VERSION_MINOR(prop.apiVersion),
+ (int)VK_VERSION_PATCH(prop.apiVersion));
+ vk->physd = devices[i];
+ vk->limits = prop.limits;
+ talloc_free(devices);
+ return true;
+ }
+ }
+
+error:
+ MP_VERBOSE(vk, "Found no suitable device, giving up.\n");
+ talloc_free(devices);
+ return false;
+}
+
+bool mpvk_pick_surface_format(struct mpvk_ctx *vk)
+{
+ assert(vk->physd);
+
+ VkSurfaceFormatKHR *formats = NULL;
+ int num;
+
+ // Enumerate through the surface formats and find one that we can map to
+ // a ra_format
+ VK(vkGetPhysicalDeviceSurfaceFormatsKHR(vk->physd, vk->surf, &num, NULL));
+ formats = talloc_array(NULL, VkSurfaceFormatKHR, num);
+ VK(vkGetPhysicalDeviceSurfaceFormatsKHR(vk->physd, vk->surf, &num, formats));
+
+ for (int i = 0; i < num; i++) {
+ // A value of VK_FORMAT_UNDEFINED means we can pick anything we want
+ if (formats[i].format == VK_FORMAT_UNDEFINED) {
+ vk->surf_format = (VkSurfaceFormatKHR) {
+ .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
+ .format = VK_FORMAT_R16G16B16A16_UNORM,
+ };
+ break;
+ }
+
+ if (formats[i].colorSpace != VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
+ continue;
+
+ // Format whitelist, since we want only >= 8 bit _UNORM formats
+ switch (formats[i].format) {
+ case VK_FORMAT_R8G8B8_UNORM:
+ case VK_FORMAT_B8G8R8_UNORM:
+ case VK_FORMAT_R8G8B8A8_UNORM:
+ case VK_FORMAT_B8G8R8A8_UNORM:
+ case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
+ case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
+ case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+ case VK_FORMAT_R16G16B16_UNORM:
+ case VK_FORMAT_R16G16B16A16_UNORM:
+ break; // accept
+ default: continue;
+ }
+
+ vk->surf_format = formats[i];
+ break;
+ }
+
+ talloc_free(formats);
+
+ if (!vk->surf_format.format)
+ goto error;
+
+ return true;
+
+error:
+ MP_ERR(vk, "Failed picking surface format!\n");
+ talloc_free(formats);
+ return false;
+}
+
+static bool vk_cmdpool_init(struct mpvk_ctx *vk, VkDeviceQueueCreateInfo qinfo,
+ VkQueueFamilyProperties props,
+ struct vk_cmdpool **out)
+{
+ struct vk_cmdpool *pool = *out = talloc_ptrtype(NULL, pool);
+ *pool = (struct vk_cmdpool) {
+ .qf = qinfo.queueFamilyIndex,
+ .props = props,
+ .qcount = qinfo.queueCount,
+ };
+
+ for (int n = 0; n < pool->qcount; n++)
+ vkGetDeviceQueue(vk->dev, pool->qf, n, &pool->queues[n]);
+
+ VkCommandPoolCreateInfo cinfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
+ VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = pool->qf,
+ };
+
+ VK(vkCreateCommandPool(vk->dev, &cinfo, MPVK_ALLOCATOR, &pool->pool));
+
+ VkCommandBufferAllocateInfo ainfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = pool->pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = MPVK_MAX_CMDS,
+ };
+
+ VkCommandBuffer cmdbufs[MPVK_MAX_CMDS];
+ VK(vkAllocateCommandBuffers(vk->dev, &ainfo, cmdbufs));
+
+ for (int n = 0; n < MPVK_MAX_CMDS; n++) {
+ struct vk_cmd *cmd = &pool->cmds[n];
+ cmd->pool = pool;
+ cmd->buf = cmdbufs[n];
+
+ VkFenceCreateInfo finfo = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT,
+ };
+
+ VK(vkCreateFence(vk->dev, &finfo, MPVK_ALLOCATOR, &cmd->fence));
+
+ VkSemaphoreCreateInfo sinfo = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ };
+
+ VK(vkCreateSemaphore(vk->dev, &sinfo, MPVK_ALLOCATOR, &cmd->done));
+ }
+
+ return true;
+
+error:
+ return false;
+}
+
+bool mpvk_device_init(struct mpvk_ctx *vk, struct mpvk_device_opts opts)
+{
+ assert(vk->physd);
+ void *tmp = talloc_new(NULL);
+
+ // Enumerate the queue families and find suitable families for each task
+ int qfnum;
+ vkGetPhysicalDeviceQueueFamilyProperties(vk->physd, &qfnum, NULL);
+ VkQueueFamilyProperties *qfs = talloc_array(tmp, VkQueueFamilyProperties, qfnum);
+ vkGetPhysicalDeviceQueueFamilyProperties(vk->physd, &qfnum, qfs);
+
+ MP_VERBOSE(vk, "Queue families supported by device:\n");
+
+ for (int i = 0; i < qfnum; i++) {
+ MP_VERBOSE(vk, " QF %d: flags 0x%x num %d\n", i,
+ (unsigned)qfs[i].queueFlags, (int)qfs[i].queueCount);
+ }
+
+ // For most of our rendering operations, we want to use one "primary" pool,
+ // so just pick the queue family with the most features.
+ int idx = -1;
+ for (int i = 0; i < qfnum; i++) {
+ if (!(qfs[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
+ continue;
+
+ // QF supports more features
+ if (idx < 0 || qfs[i].queueFlags > qfs[idx].queueFlags)
+ idx = i;
+
+ // QF supports more queues (at the same specialization level)
+ if (qfs[i].queueFlags == qfs[idx].queueFlags &&
+ qfs[i].queueCount > qfs[idx].queueCount)
+ {
+ idx = i;
+ }
+ }
+
+ // Vulkan requires at least one GRAPHICS queue, so if this fails something
+ // is horribly wrong.
+ assert(idx >= 0);
+
+ // Ensure we can actually present to the surface using this queue
+ VkBool32 sup;
+ VK(vkGetPhysicalDeviceSurfaceSupportKHR(vk->physd, idx, vk->surf, &sup));
+ if (!sup) {
+ MP_ERR(vk, "Queue family does not support surface presentation!\n");
+ goto error;
+ }
+
+ // Now that we know which queue families we want, we can create the logical
+ // device
+ assert(opts.queue_count <= MPVK_MAX_QUEUES);
+ static const float priorities[MPVK_MAX_QUEUES] = {0};
+ VkDeviceQueueCreateInfo qinfo = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex = idx,
+ .queueCount = MPMIN(qfs[idx].queueCount, opts.queue_count),
+ .pQueuePriorities = priorities,
+ };
+
+ const char **exts = NULL;
+ int num_exts = 0;
+ MP_TARRAY_APPEND(tmp, exts, num_exts, VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+ if (vk->spirv->required_ext)
+ MP_TARRAY_APPEND(tmp, exts, num_exts, vk->spirv->required_ext);
+
+ VkDeviceCreateInfo dinfo = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount = 1,
+ .pQueueCreateInfos = &qinfo,
+ .ppEnabledExtensionNames = exts,
+ .enabledExtensionCount = num_exts,
+ };
+
+ MP_VERBOSE(vk, "Creating vulkan device with extensions:\n");
+ for (int i = 0; i < num_exts; i++)
+ MP_VERBOSE(vk, " %s\n", exts[i]);
+
+ VK(vkCreateDevice(vk->physd, &dinfo, MPVK_ALLOCATOR, &vk->dev));
+
+ vk_malloc_init(vk);
+
+ // Create the vk_cmdpools and all required queues / synchronization objects
+ if (!vk_cmdpool_init(vk, qinfo, qfs[idx], &vk->pool))
+ goto error;
+
+ talloc_free(tmp);
+ return true;
+
+error:
+ MP_ERR(vk, "Failed creating logical device!\n");
+ talloc_free(tmp);
+ return false;
+}
+
+static void run_callbacks(struct mpvk_ctx *vk, struct vk_cmd *cmd)
+{
+ for (int i = 0; i < cmd->num_callbacks; i++) {
+ struct vk_callback *cb = &cmd->callbacks[i];
+ cb->run(cb->priv, cb->arg);
+ *cb = (struct vk_callback){0};
+ }
+
+ cmd->num_callbacks = 0;
+
+ // Also reset vk->last_cmd in case this was the last command to run
+ if (vk->last_cmd == cmd)
+ vk->last_cmd = NULL;
+}
+
+static void wait_for_cmds(struct mpvk_ctx *vk, struct vk_cmd cmds[], int num)
+{
+ if (!num)
+ return;
+
+ VkFence fences[MPVK_MAX_CMDS];
+ for (int i = 0; i < num; i++)
+ fences[i] = cmds[i].fence;
+
+ vkWaitForFences(vk->dev, num, fences, true, UINT64_MAX);
+
+ for (int i = 0; i < num; i++)
+ run_callbacks(vk, &cmds[i]);
+}
+
+void mpvk_pool_wait_idle(struct mpvk_ctx *vk, struct vk_cmdpool *pool)
+{
+ if (!pool)
+ return;
+
+ int idx = pool->cindex, pidx = pool->cindex_pending;
+ if (pidx < idx) { // range doesn't wrap
+ wait_for_cmds(vk, &pool->cmds[pidx], idx - pidx);
+ } else if (pidx > idx) { // range wraps
+ wait_for_cmds(vk, &pool->cmds[pidx], MPVK_MAX_CMDS - pidx);
+ wait_for_cmds(vk, &pool->cmds[0], idx);
+ }
+ pool->cindex_pending = pool->cindex;
+}
+
+void mpvk_dev_wait_idle(struct mpvk_ctx *vk)
+{
+ mpvk_pool_wait_idle(vk, vk->pool);
+}
+
+void mpvk_pool_poll_cmds(struct mpvk_ctx *vk, struct vk_cmdpool *pool,
+ uint64_t timeout)
+{
+ if (!pool)
+ return;
+
+ // If requested, hard block until at least one command completes
+ if (timeout > 0 && pool->cindex_pending != pool->cindex) {
+ vkWaitForFences(vk->dev, 1, &pool->cmds[pool->cindex_pending].fence,
+ true, timeout);
+ }
+
+ // Lazily garbage collect the commands based on their status
+ while (pool->cindex_pending != pool->cindex) {
+ struct vk_cmd *cmd = &pool->cmds[pool->cindex_pending];
+ VkResult res = vkGetFenceStatus(vk->dev, cmd->fence);
+ if (res != VK_SUCCESS)
+ break;
+ run_callbacks(vk, cmd);
+ pool->cindex_pending++;
+ pool->cindex_pending %= MPVK_MAX_CMDS;
+ }
+}
+
+void mpvk_dev_poll_cmds(struct mpvk_ctx *vk, uint32_t timeout)
+{
+ mpvk_pool_poll_cmds(vk, vk->pool, timeout);
+}
+
+void vk_dev_callback(struct mpvk_ctx *vk, vk_cb callback, void *p, void *arg)
+{
+ if (vk->last_cmd) {
+ vk_cmd_callback(vk->last_cmd, callback, p, arg);
+ } else {
+ // The device was already idle, so we can just immediately call it
+ callback(p, arg);
+ }
+}
+
+void vk_cmd_callback(struct vk_cmd *cmd, vk_cb callback, void *p, void *arg)
+{
+ MP_TARRAY_GROW(NULL, cmd->callbacks, cmd->num_callbacks);
+ cmd->callbacks[cmd->num_callbacks++] = (struct vk_callback) {
+ .run = callback,
+ .priv = p,
+ .arg = arg,
+ };
+}
+
+void vk_cmd_dep(struct vk_cmd *cmd, VkSemaphore dep,
+ VkPipelineStageFlags depstage)
+{
+ assert(cmd->num_deps < MPVK_MAX_CMD_DEPS);
+ cmd->deps[cmd->num_deps] = dep;
+ cmd->depstages[cmd->num_deps++] = depstage;
+}
+
+struct vk_cmd *vk_cmd_begin(struct mpvk_ctx *vk, struct vk_cmdpool *pool)
+{
+ // Garbage collect the cmdpool first
+ mpvk_pool_poll_cmds(vk, pool, 0);
+
+ int next = (pool->cindex + 1) % MPVK_MAX_CMDS;
+ if (next == pool->cindex_pending) {
+ MP_ERR(vk, "No free command buffers!\n");
+ goto error;
+ }
+
+ struct vk_cmd *cmd = &pool->cmds[pool->cindex];
+ pool->cindex = next;
+
+ VK(vkResetCommandBuffer(cmd->buf, 0));
+
+ VkCommandBufferBeginInfo binfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+
+ VK(vkBeginCommandBuffer(cmd->buf, &binfo));
+
+ return cmd;
+
+error:
+ return NULL;
+}
+
+bool vk_cmd_submit(struct mpvk_ctx *vk, struct vk_cmd *cmd, VkSemaphore *done)
+{
+ VK(vkEndCommandBuffer(cmd->buf));
+
+ struct vk_cmdpool *pool = cmd->pool;
+ VkQueue queue = pool->queues[pool->qindex];
+
+ VkSubmitInfo sinfo = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd->buf,
+ .waitSemaphoreCount = cmd->num_deps,
+ .pWaitSemaphores = cmd->deps,
+ .pWaitDstStageMask = cmd->depstages,
+ };
+
+ if (done) {
+ sinfo.signalSemaphoreCount = 1;
+ sinfo.pSignalSemaphores = &cmd->done;
+ *done = cmd->done;
+ }
+
+ VK(vkResetFences(vk->dev, 1, &cmd->fence));
+ VK(vkQueueSubmit(queue, 1, &sinfo, cmd->fence));
+ MP_TRACE(vk, "Submitted command on queue %p (QF %d)\n", (void *)queue,
+ pool->qf);
+
+ for (int i = 0; i < cmd->num_deps; i++)
+ cmd->deps[i] = NULL;
+ cmd->num_deps = 0;
+
+ vk->last_cmd = cmd;
+ return true;
+
+error:
+ return false;
+}
+
+void vk_cmd_cycle_queues(struct mpvk_ctx *vk)
+{
+ struct vk_cmdpool *pool = vk->pool;
+ pool->qindex = (pool->qindex + 1) % pool->qcount;
+}
+
+const VkImageSubresourceRange vk_range = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .levelCount = 1,
+ .layerCount = 1,
+};
+
+const VkImageSubresourceLayers vk_layers = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .layerCount = 1,
+};
diff --git a/video/out/vulkan/utils.h b/video/out/vulkan/utils.h
new file mode 100644
index 0000000..0cc8a29
--- /dev/null
+++ b/video/out/vulkan/utils.h
@@ -0,0 +1,154 @@
+#pragma once
+
+#include "video/out/vo.h"
+#include "video/out/gpu/context.h"
+#include "video/mp_image.h"
+
+#include "common.h"
+#include "formats.h"
+
+#define VK_LOAD_PFN(name) PFN_##name pfn_##name = (PFN_##name) \
+ vkGetInstanceProcAddr(vk->inst, #name);
+
+// Return a human-readable name for various struct mpvk_ctx enums
+const char* vk_err(VkResult res);
+
+// Convenience macros to simplify a lot of common boilerplate
+#define VK_ASSERT(res, str) \
+ do { \
+ if (res != VK_SUCCESS) { \
+ MP_ERR(vk, str ": %s\n", vk_err(res)); \
+ goto error; \
+ } \
+ } while (0)
+
+#define VK(cmd) \
+ do { \
+ MP_TRACE(vk, #cmd "\n"); \
+ VkResult res ## __LINE__ = (cmd); \
+ VK_ASSERT(res ## __LINE__, #cmd); \
+ } while (0)
+
+// Uninits everything in the correct order
+void mpvk_uninit(struct mpvk_ctx *vk);
+
+// Initialization functions: As a rule of thumb, these need to be called in
+// this order, followed by vk_malloc_init, followed by RA initialization, and
+// finally followed by vk_swchain initialization.
+
+// Create a vulkan instance. Returns VK_NULL_HANDLE on failure
+bool mpvk_instance_init(struct mpvk_ctx *vk, struct mp_log *log,
+ const char *surf_ext_name, bool debug);
+
+// Generate a VkSurfaceKHR usable for video output. Returns VK_NULL_HANDLE on
+// failure. Must be called after mpvk_instance_init.
+bool mpvk_surface_init(struct vo *vo, struct mpvk_ctx *vk);
+
+// Find a suitable physical device for use with rendering and which supports
+// the surface.
+// name: only match a device with this name
+// sw: also allow software/virtual devices
+bool mpvk_find_phys_device(struct mpvk_ctx *vk, const char *name, bool sw);
+
+// Pick a suitable surface format that's supported by this physical device.
+bool mpvk_pick_surface_format(struct mpvk_ctx *vk);
+
+struct mpvk_device_opts {
+ int queue_count; // number of queues to use
+};
+
+// Create a logical device and initialize the vk_cmdpools
+bool mpvk_device_init(struct mpvk_ctx *vk, struct mpvk_device_opts opts);
+
+// Wait until all commands submitted to all queues have completed
+void mpvk_pool_wait_idle(struct mpvk_ctx *vk, struct vk_cmdpool *pool);
+void mpvk_dev_wait_idle(struct mpvk_ctx *vk);
+
+// Wait until at least one command submitted to any queue has completed, and
+// process the callbacks. Good for event loops that need to delay until a
+// command completes. Will block at most `timeout` nanoseconds. If used with
+// 0, it only garbage collects completed commands without blocking.
+void mpvk_pool_poll_cmds(struct mpvk_ctx *vk, struct vk_cmdpool *pool,
+ uint64_t timeout);
+void mpvk_dev_poll_cmds(struct mpvk_ctx *vk, uint32_t timeout);
+
+// Since lots of vulkan operations need to be done lazily once the affected
+// resources are no longer in use, provide an abstraction for tracking these.
+// In practice, these are only checked and run when submitting new commands, so
+// the actual execution may be delayed by a frame.
+typedef void (*vk_cb)(void *priv, void *arg);
+
+struct vk_callback {
+ vk_cb run;
+ void *priv;
+ void *arg; // as a convenience, you also get to pass an arg for "free"
+};
+
+// Associate a callback with the completion of all currently pending commands.
+// This will essentially run once the device is completely idle.
+void vk_dev_callback(struct mpvk_ctx *vk, vk_cb callback, void *p, void *arg);
+
+#define MPVK_MAX_CMD_DEPS 8
+
+// Helper wrapper around command buffers that also track dependencies,
+// callbacks and synchronization primitives
+struct vk_cmd {
+ struct vk_cmdpool *pool; // pool it was allocated from
+ VkCommandBuffer buf;
+ VkFence fence; // the fence guards cmd buffer reuse
+ VkSemaphore done; // the semaphore signals when execution is done
+ // The semaphores represent dependencies that need to complete before
+ // this command can be executed. These are *not* owned by the vk_cmd
+ VkSemaphore deps[MPVK_MAX_CMD_DEPS];
+ VkPipelineStageFlags depstages[MPVK_MAX_CMD_DEPS];
+ int num_deps;
+ // Since VkFences are useless, we have to manually track "callbacks"
+ // to fire once the VkFence completes. These are used for multiple purposes,
+ // ranging from garbage collection (resource deallocation) to fencing.
+ struct vk_callback *callbacks;
+ int num_callbacks;
+};
+
+// Associate a callback with the completion of the current command. This
+// bool will be set to `true` once the command completes, or shortly thereafter.
+void vk_cmd_callback(struct vk_cmd *cmd, vk_cb callback, void *p, void *arg);
+
+// Associate a dependency for the current command. This semaphore must signal
+// by the corresponding stage before the command may execute.
+void vk_cmd_dep(struct vk_cmd *cmd, VkSemaphore dep,
+ VkPipelineStageFlags depstage);
+
+#define MPVK_MAX_QUEUES 8
+#define MPVK_MAX_CMDS 64
+
+// Command pool / queue family hybrid abstraction
+struct vk_cmdpool {
+ VkQueueFamilyProperties props;
+ uint32_t qf; // queue family index
+ VkCommandPool pool;
+ VkQueue queues[MPVK_MAX_QUEUES];
+ int qcount;
+ int qindex;
+ // Command buffers associated with this queue
+ struct vk_cmd cmds[MPVK_MAX_CMDS];
+ int cindex;
+ int cindex_pending;
+};
+
+// Fetch the next command buffer from a command pool and begin recording to it.
+// Returns NULL on failure.
+struct vk_cmd *vk_cmd_begin(struct mpvk_ctx *vk, struct vk_cmdpool *pool);
+
+// Finish the currently recording command buffer and submit it for execution.
+// If `done` is not NULL, it will be set to a semaphore that will signal once
+// the command completes. (And MUST have a corresponding semaphore wait)
+// Returns whether successful.
+bool vk_cmd_submit(struct mpvk_ctx *vk, struct vk_cmd *cmd, VkSemaphore *done);
+
+// Rotate the queues for each vk_cmdpool. Call this once per frame to ensure
+// good parallelism between frames when using multiple queues
+void vk_cmd_cycle_queues(struct mpvk_ctx *vk);
+
+// Predefined structs for a simple non-layered, non-mipped image
+extern const VkImageSubresourceRange vk_range;
+extern const VkImageSubresourceLayers vk_layers;
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index b93a4fd..feeae81 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -62,8 +62,12 @@ typedef enum MONITOR_DPI_TYPE {
} MONITOR_DPI_TYPE;
#endif
+#define rect_w(r) ((r).right - (r).left)
+#define rect_h(r) ((r).bottom - (r).top)
+
struct w32_api {
HRESULT (WINAPI *pGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
+ BOOL (WINAPI *pImmDisableIME)(DWORD);
};
struct vo_w32_state {
@@ -84,15 +88,8 @@ struct vo_w32_state {
HWINEVENTHOOK parent_evt_hook;
HMONITOR monitor; // Handle of the current screen
- struct mp_rect screenrc; // Size and virtual position of the current screen
char *color_profile; // Path of the current screen's color profile
- // last non-fullscreen extends (updated only on fullscreen or on initialization)
- int prev_width;
- int prev_height;
- int prev_x;
- int prev_y;
-
// Has the window seen a WM_DESTROY? If so, don't call DestroyWindow again.
bool destroyed;
@@ -102,11 +99,10 @@ struct vo_w32_state {
bool current_fs;
bool toggle_fs; // whether the current fullscreen state needs to be switched
- // currently known window state
- int window_x;
- int window_y;
- int dw;
- int dh;
+ RECT windowrc; // currently known window rect
+ RECT screenrc; // current screen rect
+ // last non-fullscreen rect, updated only on fullscreen or on initialization
+ RECT prev_windowrc;
// video size
uint32_t o_dwidth;
@@ -130,6 +126,9 @@ struct vo_w32_state {
// UTF-16 decoding state for WM_CHAR and VK_PACKET
int high_surrogate;
+ // Whether to fit the window on screen on next window state updating
+ bool fit_on_screen;
+
ITaskbarList2 *taskbar_list;
ITaskbarList3 *taskbar_list3;
UINT tbtnCreatedMsg;
@@ -140,6 +139,7 @@ struct vo_w32_state {
// updates on move/resize/displaychange
double display_fps;
+ bool moving;
bool snapped;
int snap_dx;
int snap_dy;
@@ -182,16 +182,16 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y)
if (mouse.y < frame_size) {
if (mouse.x < diagonal_width)
return HTTOPLEFT;
- if (mouse.x >= w32->dw - diagonal_width)
+ if (mouse.x >= rect_w(w32->windowrc) - diagonal_width)
return HTTOPRIGHT;
return HTTOP;
}
// Hit-test bottom border
- if (mouse.y >= w32->dh - frame_size) {
+ if (mouse.y >= rect_h(w32->windowrc) - frame_size) {
if (mouse.x < diagonal_width)
return HTBOTTOMLEFT;
- if (mouse.x >= w32->dw - diagonal_width)
+ if (mouse.x >= rect_w(w32->windowrc) - diagonal_width)
return HTBOTTOMRIGHT;
return HTBOTTOM;
}
@@ -199,7 +199,7 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y)
// Hit-test side borders
if (mouse.x < frame_size)
return HTLEFT;
- if (mouse.x >= w32->dw - frame_size)
+ if (mouse.x >= rect_w(w32->windowrc) - frame_size)
return HTRIGHT;
return HTCLIENT;
}
@@ -607,6 +607,9 @@ static void update_playback_state(struct vo_w32_state *w32)
static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
{
+ if (w32->parent || w32->current_fs || IsMaximized(w32->window))
+ return false;
+
if (!w32->opts->snap_window) {
w32->snapped = false;
return false;
@@ -616,16 +619,24 @@ static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
POINT cursor;
if (!GetWindowRect(w32->window, &rect) || !GetCursorPos(&cursor))
return false;
- // Check for aero snapping
- if ((rc->right - rc->left != rect.right - rect.left) ||
- (rc->bottom - rc->top != rect.bottom - rect.top))
+ // Check if window is going to be aero-snapped
+ if (rect_w(*rc) != rect_w(rect) || rect_h(*rc) != rect_h(rect))
+ return false;
+
+ // Check if window has already been aero-snapped
+ WINDOWPLACEMENT wp = {0};
+ wp.length = sizeof(wp);
+ if (!GetWindowPlacement(w32->window, &wp))
+ return false;
+ RECT wr = wp.rcNormalPosition;
+ if (rect_w(*rc) != rect_w(wr) || rect_h(*rc) != rect_h(wr))
return false;
MONITORINFO mi = { .cbSize = sizeof(mi) };
if (!GetMonitorInfoW(w32->monitor, &mi))
return false;
// Get the work area to let the window snap to taskbar
- RECT wr = mi.rcWork;
+ wr = mi.rcWork;
// Check for invisible borders and adjust the work area size
RECT frame = {0};
@@ -706,15 +717,10 @@ static void update_screen_rect(struct vo_w32_state *w32)
// Handle --fs-screen=all
if (w32->current_fs && screen == -2) {
- struct mp_rect rc = {
- GetSystemMetrics(SM_XVIRTUALSCREEN),
- GetSystemMetrics(SM_YVIRTUALSCREEN),
- GetSystemMetrics(SM_CXVIRTUALSCREEN),
- GetSystemMetrics(SM_CYVIRTUALSCREEN),
- };
- rc.x1 += rc.x0;
- rc.y1 += rc.y0;
- w32->screenrc = rc;
+ const int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ const int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ SetRect(&w32->screenrc, x, y, x + GetSystemMetrics(SM_CXVIRTUALSCREEN),
+ y + GetSystemMetrics(SM_CYVIRTUALSCREEN));
return;
}
@@ -734,10 +740,7 @@ static void update_screen_rect(struct vo_w32_state *w32)
MONITORINFO mi = { .cbSize = sizeof(mi) };
GetMonitorInfoW(mon, &mi);
- w32->screenrc = (struct mp_rect){
- mi.rcMonitor.left, mi.rcMonitor.top,
- mi.rcMonitor.right, mi.rcMonitor.bottom,
- };
+ w32->screenrc = mi.rcMonitor;
}
static DWORD update_style(struct vo_w32_state *w32, DWORD style)
@@ -754,139 +757,148 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style)
return style;
}
-// Update the window title, position, size, and border style.
-static void reinit_window_state(struct vo_w32_state *w32)
+static void update_window_style(struct vo_w32_state *w32)
{
- HWND layer = HWND_NOTOPMOST;
- RECT r;
-
if (w32->parent)
return;
- bool new_fs = w32->toggle_fs ? !w32->current_fs : w32->opts->fullscreen;
- bool toggle_fs = w32->current_fs != new_fs;
- w32->current_fs = new_fs;
- w32->toggle_fs = false;
+ // SetWindowLongPtr can trigger a WM_SIZE event, so window rect
+ // has to be saved now and restored after setting the new style.
+ const RECT wr = w32->windowrc;
+ const DWORD style = GetWindowLongPtrW(w32->window, GWL_STYLE);
+ SetWindowLongPtrW(w32->window, GWL_STYLE, update_style(w32, style));
+ w32->windowrc = wr;
+}
- if (w32->taskbar_list) {
- ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list,
- w32->window, w32->current_fs);
+// Adjust rc size and position if its size is larger than rc2.
+// returns true if the rectangle was modified.
+static bool fit_rect(RECT *rc, RECT *rc2)
+{
+ // Calculate old size and maximum new size
+ int o_w = rect_w(*rc), o_h = rect_h(*rc);
+ int n_w = rect_w(*rc2), n_h = rect_h(*rc2);
+ if (o_w <= n_w && o_h <= n_h)
+ return false;
+
+ // Apply letterboxing
+ const float o_asp = o_w / (float)MPMAX(o_h, 1);
+ const float n_asp = n_w / (float)MPMAX(n_h, 1);
+ if (o_asp > n_asp) {
+ n_h = n_w / o_asp;
+ } else {
+ n_w = n_h * o_asp;
}
- DWORD style = update_style(w32, GetWindowLongPtrW(w32->window, GWL_STYLE));
+ // Calculate new position and save the rect
+ const int x = rc->left + o_w / 2 - n_w / 2;
+ const int y = rc->top + o_h / 2 - n_h / 2;
+ SetRect(rc, x, y, x + n_w, y + n_h);
+ return true;
+}
- if (w32->opts->ontop)
- layer = HWND_TOPMOST;
+// Adjust window size and position if its size is larger than the screen size.
+static void fit_window_on_screen(struct vo_w32_state *w32)
+{
+ if (w32->parent || w32->current_fs || IsMaximized(w32->window))
+ return;
- // xxx not sure if this can trigger any unwanted messages (WM_MOVE/WM_SIZE)
- update_screen_rect(w32);
+ RECT screen = w32->screenrc;
+ if (w32->opts->border && w32->opts->fit_border)
+ subtract_window_borders(w32->window, &screen);
- int screen_w = w32->screenrc.x1 - w32->screenrc.x0;
- int screen_h = w32->screenrc.y1 - w32->screenrc.y0;
+ if (fit_rect(&w32->windowrc, &screen)) {
+ MP_VERBOSE(w32, "adjusted window bounds: %d:%d:%d:%d\n",
+ (int)w32->windowrc.left, (int)w32->windowrc.top,
+ (int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
+ }
+}
- if (w32->current_fs) {
- // Save window position and size when switching to fullscreen.
- if (toggle_fs) {
- w32->prev_width = w32->dw;
- w32->prev_height = w32->dh;
- w32->prev_x = w32->window_x;
- w32->prev_y = w32->window_y;
- MP_VERBOSE(w32, "save window bounds: %d:%d:%d:%d\n",
- w32->prev_x, w32->prev_y, w32->prev_width, w32->prev_height);
- }
+// Calculate new fullscreen state and change window size and position.
+// returns true if fullscreen state was changed.
+static bool update_fullscreen_state(struct vo_w32_state *w32)
+{
+ if (w32->parent)
+ return false;
- w32->window_x = w32->screenrc.x0;
- w32->window_y = w32->screenrc.y0;
- w32->dw = screen_w;
- w32->dh = screen_h;
- } else {
- if (toggle_fs) {
- // Restore window position and size when switching from fullscreen.
- MP_VERBOSE(w32, "restore window bounds: %d:%d:%d:%d\n",
- w32->prev_x, w32->prev_y, w32->prev_width, w32->prev_height);
- w32->dw = w32->prev_width;
- w32->dh = w32->prev_height;
- w32->window_x = w32->prev_x;
- w32->window_y = w32->prev_y;
- }
+ bool new_fs = w32->opts->fullscreen;
+ if (w32->toggle_fs) {
+ new_fs = !w32->current_fs;
+ w32->toggle_fs = false;
}
- r.left = w32->window_x;
- r.right = r.left + w32->dw;
- r.top = w32->window_y;
- r.bottom = r.top + w32->dh;
-
- SetWindowLongPtrW(w32->window, GWL_STYLE, style);
-
- RECT cr = r;
- add_window_borders(w32->window, &r);
- // Check on client area size instead of window size on --fit-border=no
- long o_w;
- long o_h;
- if( w32->opts->fit_border ) {
- o_w = r.right - r.left;
- o_h = r.bottom - r.top;
- } else {
- o_w = cr.right - cr.left;
- o_h = cr.bottom - cr.top;
- }
+ bool toggle_fs = w32->current_fs != new_fs;
+ w32->current_fs = new_fs;
- if ( !w32->current_fs && ( o_w > screen_w || o_h > screen_h ) )
- {
- MP_VERBOSE(w32, "requested window size larger than the screen\n");
- // Use the aspect of the client area, not the full window size.
- // Basically, try to compute the maximum window size.
- long n_w;
- long n_h;
- if( w32->opts->fit_border ) {
- n_w = screen_w - (r.right - cr.right) - (cr.left - r.left);
- n_h = screen_h - (r.bottom - cr.bottom) - (cr.top - r.top);
- } else {
- n_w = screen_w;
- n_h = screen_h;
- }
- // Letterbox
- double asp = (cr.right - cr.left) / (double)(cr.bottom - cr.top);
- double s_asp = n_w / (double)n_h;
- if (asp > s_asp) {
- n_h = n_w / asp;
+ update_screen_rect(w32);
+
+ if (toggle_fs) {
+ RECT rc;
+ char msg[50];
+ if (w32->current_fs) {
+ // Save window rect when switching to fullscreen.
+ rc = w32->prev_windowrc = w32->windowrc;
+ sprintf(msg, "save window bounds");
} else {
- n_w = n_h * asp;
+ // Restore window rect when switching from fullscreen.
+ rc = w32->windowrc = w32->prev_windowrc;
+ sprintf(msg, "restore window bounds");
}
- // Save new size
- w32->dw = n_w;
- w32->dh = n_h;
- // Get old window center
- long o_cx = r.left + (r.right - r.left) / 2;
- long o_cy = r.top + (r.bottom - r.top) / 2;
- // Add window borders to the new window size
- r = (RECT){.right = n_w, .bottom = n_h};
- add_window_borders(w32->window, &r);
- // Get top and left border size for client area position calculation
- long b_top = -r.top;
- long b_left = -r.left;
- // Center the final window around the old window center
- n_w = r.right - r.left;
- n_h = r.bottom - r.top;
- r.left = o_cx - n_w / 2;
- r.top = o_cy - n_h / 2;
- r.right = r.left + n_w;
- r.bottom = r.top + n_h;
- // Save new client area position
- w32->window_x = r.left + b_left;
- w32->window_y = r.top + b_top;
+ MP_VERBOSE(w32, "%s: %d:%d:%d:%d\n", msg,
+ (int)rc.left, (int)rc.top, (int)rect_w(rc), (int)rect_h(rc));
}
+ if (w32->current_fs)
+ w32->windowrc = w32->screenrc;
+
MP_VERBOSE(w32, "reset window bounds: %d:%d:%d:%d\n",
- (int) r.left, (int) r.top, (int)(r.right - r.left),
- (int)(r.bottom - r.top));
+ (int)w32->windowrc.left, (int)w32->windowrc.top,
+ (int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
+ return toggle_fs;
+}
+
+static void update_window_state(struct vo_w32_state *w32)
+{
+ if (w32->parent)
+ return;
+
+ RECT wr = w32->windowrc;
+ add_window_borders(w32->window, &wr);
- SetWindowPos(w32->window, layer, r.left, r.top, r.right - r.left,
- r.bottom - r.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
+ SetWindowPos(w32->window, w32->opts->ontop ? HWND_TOPMOST : HWND_NOTOPMOST,
+ wr.left, wr.top, rect_w(wr), rect_h(wr),
+ SWP_FRAMECHANGED | SWP_SHOWWINDOW);
+
+ // Notify the taskbar about the fullscreen state only after the window
+ // is visible, to make sure the taskbar item has already been created
+ if (w32->taskbar_list) {
+ ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list,
+ w32->window, w32->current_fs);
+ }
signal_events(w32, VO_EVENT_RESIZE);
}
+static void reinit_window_state(struct vo_w32_state *w32)
+{
+ if (w32->parent)
+ return;
+
+ // The order matters: fs state should be updated prior to changing styles
+ bool toggle_fs = update_fullscreen_state(w32);
+ update_window_style(w32);
+
+ // Assume that the window has already been fit on screen before switching fs
+ if (!toggle_fs || w32->fit_on_screen) {
+ fit_window_on_screen(w32);
+ // The fullscreen state might still be active, so set the flag
+ // to fit on screen next time the window leaves the fullscreen.
+ w32->fit_on_screen = w32->current_fs;
+ }
+
+ // Show and activate the window after all window state parameters were set
+ update_window_state(w32);
+}
+
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
@@ -917,25 +929,26 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
signal_events(w32, VO_EVENT_EXPOSE);
break;
case WM_MOVE: {
- POINT p = {0};
- ClientToScreen(w32->window, &p);
- w32->window_x = p.x;
- w32->window_y = p.y;
+ const int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
+ OffsetRect(&w32->windowrc, x - w32->windowrc.left,
+ y - w32->windowrc.top);
// Window may intersect with new monitors (see VOCTRL_GET_DISPLAY_NAMES)
signal_events(w32, VO_EVENT_WIN_STATE);
update_display_info(w32); // if we moved between monitors
- MP_DBG(w32, "move window: %d:%d\n", w32->window_x, w32->window_y);
+ MP_DBG(w32, "move window: %d:%d\n", x, y);
break;
}
case WM_MOVING: {
+ w32->moving = true;
RECT *rc = (RECT*)lParam;
if (snap_to_screen_edges(w32, rc))
return TRUE;
break;
}
case WM_ENTERSIZEMOVE:
+ w32->moving = true;
if (w32->snapped) {
// Save the cursor offset from the window borders,
// so the player window can be unsnapped later
@@ -947,13 +960,19 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
}
}
break;
+ case WM_EXITSIZEMOVE:
+ w32->moving = false;
+ break;
case WM_SIZE: {
- RECT r;
- if (GetClientRect(w32->window, &r) && r.right > 0 && r.bottom > 0) {
- w32->dw = r.right;
- w32->dh = r.bottom;
+ if (w32->moving)
+ w32->snapped = false;
+
+ const int w = LOWORD(lParam), h = HIWORD(lParam);
+ if (w > 0 && h > 0) {
+ w32->windowrc.right = w32->windowrc.left + w;
+ w32->windowrc.bottom = w32->windowrc.top + h;
signal_events(w32, VO_EVENT_RESIZE);
- MP_VERBOSE(w32, "resize window: %d:%d\n", w32->dw, w32->dh);
+ MP_VERBOSE(w32, "resize window: %d:%d\n", w, h);
}
// Window may have been minimized or restored
@@ -971,7 +990,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
// (subtracting the window borders)
RECT r = *rc;
subtract_window_borders(w32->window, &r);
- int c_w = r.right - r.left, c_h = r.bottom - r.top;
+ int c_w = rect_w(r), c_h = rect_h(r);
float aspect = w32->o_dwidth / (float) MPMAX(w32->o_dheight, 1);
int d_w = c_h * aspect - c_w;
int d_h = c_w / aspect - c_h;
@@ -988,8 +1007,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
update_display_info(w32);
break;
case WM_CLOSE:
- // Don't actually allow it to destroy the window, or whatever else it
- // is that will make us lose WM_USER wakeups.
+ // Don't destroy the window yet to not lose wakeup events.
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
return 0;
case WM_NCDESTROY: // Sometimes only WM_NCDESTROY is received in --wid mode
@@ -1260,56 +1278,51 @@ static void run_message_loop(struct vo_w32_state *w32)
static void gui_thread_reconfig(void *ptr)
{
struct vo_w32_state *w32 = ptr;
-
struct vo *vo = w32->vo;
struct vo_win_geometry geo;
- vo_calc_window_geometry(vo, &w32->screenrc, &geo);
+ struct mp_rect screen = { w32->screenrc.left, w32->screenrc.top,
+ w32->screenrc.right, w32->screenrc.bottom };
+ vo_calc_window_geometry(vo, &screen, &geo);
vo_apply_window_geometry(vo, &geo);
- bool reset_size = w32->o_dwidth != vo->dwidth || w32->o_dheight != vo->dheight;
- bool pos_init = false;
+ bool reset_size = w32->o_dwidth != vo->dwidth ||
+ w32->o_dheight != vo->dheight;
w32->o_dwidth = vo->dwidth;
w32->o_dheight = vo->dheight;
- // the desired size is ignored in wid mode, it always matches the window size.
- if (!w32->parent) {
- if (w32->window_bounds_initialized) {
- // restore vo_dwidth/vo_dheight, which are reset against our will
- // in vo_config()
- RECT r;
- GetClientRect(w32->window, &r);
- vo->dwidth = r.right;
- vo->dheight = r.bottom;
- } else {
- w32->window_bounds_initialized = true;
- reset_size = true;
- pos_init = true;
- w32->window_x = w32->prev_x = geo.win.x0;
- w32->window_y = w32->prev_y = geo.win.y0;
- }
+ if (!w32->parent && !w32->window_bounds_initialized) {
+ SetRect(&w32->windowrc, geo.win.x0, geo.win.y0,
+ geo.win.x0 + vo->dwidth, geo.win.y0 + vo->dheight);
+ w32->prev_windowrc = w32->windowrc;
+ w32->window_bounds_initialized = true;
+ w32->fit_on_screen = true;
+ goto finish;
+ }
- if (reset_size) {
- w32->prev_width = vo->dwidth = w32->o_dwidth;
- w32->prev_height = vo->dheight = w32->o_dheight;
- }
- } else {
+ // The rect which size is going to be modified.
+ RECT *rc = &w32->windowrc;
+
+ // The desired size always matches the window size in wid mode.
+ if (!reset_size || w32->parent) {
RECT r;
GetClientRect(w32->window, &r);
+ // Restore vo_dwidth and vo_dheight, which were reset in vo_config()
vo->dwidth = r.right;
vo->dheight = r.bottom;
+ } else {
+ if (w32->current_fs)
+ rc = &w32->prev_windowrc;
+ w32->fit_on_screen = true;
}
- // Recenter window around old position on new video size
- // excluding the case when initial position handled by win_state.
- if (!pos_init) {
- w32->window_x += w32->dw / 2 - vo->dwidth / 2;
- w32->window_y += w32->dh / 2 - vo->dheight / 2;
- }
- w32->dw = vo->dwidth;
- w32->dh = vo->dheight;
+ // Save new window size and position.
+ const int x = rc->left + rect_w(*rc) / 2 - vo->dwidth / 2;
+ const int y = rc->top + rect_h(*rc) / 2 - vo->dheight / 2;
+ SetRect(rc, x, y, x + vo->dwidth, y + vo->dheight);
+finish:
reinit_window_state(w32);
}
@@ -1320,25 +1333,18 @@ void vo_w32_config(struct vo *vo)
mp_dispatch_run(w32->dispatch, gui_thread_reconfig, w32);
}
-static void thread_disable_ime(void)
-{
- // Disables the IME for windows on this thread. imm32.dll must be loaded
- // dynamically to account for machines without East Asian language support.
- HMODULE imm32 = LoadLibraryW(L"imm32.dll");
- if (!imm32)
- return;
- BOOL (WINAPI *pImmDisableIME)(DWORD) = (BOOL (WINAPI*)(DWORD))
- GetProcAddress(imm32, "ImmDisableIME");
- if (pImmDisableIME)
- pImmDisableIME(0);
- FreeLibrary(imm32);
-}
-
static void w32_api_load(struct vo_w32_state *w32)
{
HMODULE shcore_dll = LoadLibraryW(L"shcore.dll");
+ // Available since Win8.1
w32->api.pGetDpiForMonitor = !shcore_dll ? NULL :
(void *)GetProcAddress(shcore_dll, "GetDpiForMonitor");
+
+ // imm32.dll must be loaded dynamically
+ // to account for machines without East Asian language support
+ HMODULE imm32_dll = LoadLibraryW(L"imm32.dll");
+ w32->api.pImmDisableIME = !imm32_dll ? NULL :
+ (void *)GetProcAddress(imm32_dll, "ImmDisableIME");
}
static void *gui_thread(void *ptr)
@@ -1350,7 +1356,10 @@ static void *gui_thread(void *ptr)
mpthread_set_name("win32 window");
w32_api_load(w32);
- thread_disable_ime();
+
+ // Disables the IME for windows on this thread
+ if (w32->api.pImmDisableIME)
+ w32->api.pImmDisableIME(0);
if (w32->opts->WinID >= 0)
w32->parent = (HWND)(intptr_t)(w32->opts->WinID);
@@ -1423,6 +1432,9 @@ static void *gui_thread(void *ptr)
EnableWindow(w32->window, 0);
w32->cursor_visible = true;
+ w32->moving = false;
+ w32->snapped = false;
+ w32->snap_dx = w32->snap_dy = 0;
update_screen_rect(w32);
@@ -1544,10 +1556,11 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
reinit_window_state(w32);
return VO_TRUE;
case VOCTRL_ONTOP:
- reinit_window_state(w32);
+ update_window_state(w32);
return VO_TRUE;
case VOCTRL_BORDER:
- reinit_window_state(w32);
+ update_window_style(w32);
+ update_window_state(w32);
return VO_TRUE;
case VOCTRL_GET_FULLSCREEN:
*(bool *)arg = w32->current_fs;
@@ -1558,8 +1571,9 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
if (!w32->window_bounds_initialized)
return VO_FALSE;
- s[0] = w32->current_fs ? w32->prev_width : w32->dw;
- s[1] = w32->current_fs ? w32->prev_height : w32->dh;
+ RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc;
+ s[0] = rect_w(*rc);
+ s[1] = rect_h(*rc);
return VO_TRUE;
}
case VOCTRL_SET_UNFS_WINDOW_SIZE: {
@@ -1567,18 +1581,13 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
if (!w32->window_bounds_initialized)
return VO_FALSE;
- if (w32->current_fs) {
- w32->prev_x += w32->prev_width / 2 - s[0] / 2;
- w32->prev_y += w32->prev_height / 2 - s[1] / 2;
- w32->prev_width = s[0];
- w32->prev_height = s[1];
- } else {
- w32->window_x += w32->dw / 2 - s[0] / 2;
- w32->window_y += w32->dh / 2 - s[1] / 2;
- w32->dw = s[0];
- w32->dh = s[1];
- }
+ RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc;
+ const int x = rc->left + rect_w(*rc) / 2 - s[0] / 2;
+ const int y = rc->top + rect_h(*rc) / 2 - s[1] / 2;
+ SetRect(rc, x, y, x + s[0], y + s[1]);
+
+ w32->fit_on_screen = true;
reinit_window_state(w32);
return VO_TRUE;
}
@@ -1648,8 +1657,8 @@ static void do_control(void *ptr)
*events |= atomic_fetch_and(&w32->event_flags, 0);
// Safe access, since caller (owner of vo) is blocked.
if (*events & VO_EVENT_RESIZE) {
- w32->vo->dwidth = w32->dw;
- w32->vo->dheight = w32->dh;
+ w32->vo->dwidth = rect_w(w32->windowrc);
+ w32->vo->dheight = rect_h(w32->windowrc);
}
}
@@ -1660,8 +1669,8 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
*events |= atomic_fetch_and(&w32->event_flags, 0);
if (*events & VO_EVENT_RESIZE) {
mp_dispatch_lock(w32->dispatch);
- vo->dwidth = w32->dw;
- vo->dheight = w32->dh;
+ vo->dwidth = rect_w(w32->windowrc);
+ vo->dheight = rect_h(w32->windowrc);
mp_dispatch_unlock(w32->dispatch);
}
return VO_TRUE;
diff --git a/video/out/wayland/buffer.c b/video/out/wayland/buffer.c
deleted file mode 100644
index dce3ca4..0000000
--- a/video/out/wayland/buffer.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * This file is part of mpv video player.
- * Copyright © 2014 Alexander Preisinger <alexander.preisinger@gmail.com>
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "buffer.h"
-#include "memfile.h"
-
-#include <unistd.h>
-#include <sys/mman.h>
-
-int8_t format_get_bytes(const format_t *fmt)
-{
- return mp_imgfmt_get_desc(fmt->mp_format).bytes[0];
-}
-
-shm_buffer_t* shm_buffer_create(uint32_t width,
- uint32_t height,
- format_t fmt,
- struct wl_shm *shm,
- const struct wl_buffer_listener *listener)
-{
- int8_t bytes = format_get_bytes(&fmt);
- uint32_t stride = SHM_BUFFER_STRIDE(width, bytes);
- uint32_t size = stride * height;
-
- shm_buffer_t *buffer = calloc(1, sizeof(shm_buffer_t));
- int fd = memfile_create(size);
-
- if (fd < 0) {
- free(buffer);
- return NULL;
- }
-
- buffer->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
- if (buffer->data == MAP_FAILED) {
- close(fd);
- free(buffer);
- return NULL;
- }
-
- buffer->shm_pool = wl_shm_create_pool(shm, fd, size);
- buffer->buffer = wl_shm_pool_create_buffer(buffer->shm_pool,
- 0, width, height, stride,
- fmt.wl_format);
-
- wl_buffer_add_listener(buffer->buffer, listener, buffer);
-
- buffer->fd = fd;
- buffer->height = height;
- buffer->stride = stride;
- buffer->format = fmt;
- buffer->bytes = bytes;
- buffer->pool_size = size;
- buffer->pending_height = 0;
- buffer->pending_width = 0;
-
- return buffer;
-}
-
-int shm_buffer_resize(shm_buffer_t *buffer, uint32_t width, uint32_t height)
-{
- uint32_t new_stride = SHM_BUFFER_STRIDE(width, buffer->bytes);
- uint32_t new_size = new_stride * height;
-
- if (SHM_BUFFER_IS_BUSY(buffer)) {
- SHM_BUFFER_SET_PNDNG_RSZ(buffer);
- buffer->pending_width = width;
- buffer->pending_height = height;
- return SHM_BUFFER_BUSY;
- }
-
- SHM_BUFFER_CLEAR_PNDNG_RSZ(buffer);
-
- if (new_size > buffer->pool_size) {
- munmap(buffer->data, buffer->pool_size);
- ftruncate(buffer->fd, new_size);
-
- buffer->data = mmap(NULL, new_size, PROT_READ | PROT_WRITE,
- MAP_SHARED, buffer->fd, 0);
-
- // TODO: the buffer should be destroyed when -1 is return
- if (buffer->data == MAP_FAILED)
- return -1;
-
- wl_shm_pool_resize(buffer->shm_pool, new_size);
- buffer->pool_size = new_size;
- }
-
- const void *listener = wl_proxy_get_listener((struct wl_proxy*)buffer->buffer);
-
- wl_buffer_destroy(buffer->buffer);
- buffer->buffer = wl_shm_pool_create_buffer(buffer->shm_pool,
- 0, width, height, new_stride,
- buffer->format.wl_format);
-
- wl_buffer_add_listener(buffer->buffer, listener, buffer);
-
- buffer->height = height;
- buffer->stride = new_stride;
-
- return 0;
-}
-
-int shm_buffer_pending_resize(shm_buffer_t *buffer)
-{
- if (SHM_BUFFER_PENDING_RESIZE(buffer)) {
- SHM_BUFFER_CLEAR_PNDNG_RSZ(buffer);
- return shm_buffer_resize(buffer, buffer->pending_width, buffer->pending_height);
- }
- else {
- return 0;
- }
-}
-
-void shm_buffer_destroy(shm_buffer_t *buffer)
-{
- if (!buffer)
- return;
-
- wl_buffer_destroy(buffer->buffer);
- wl_shm_pool_destroy(buffer->shm_pool);
- munmap(buffer->data, buffer->pool_size);
- close(buffer->fd);
- free(buffer);
-}
diff --git a/video/out/wayland/buffer.h b/video/out/wayland/buffer.h
deleted file mode 100644
index 783cd10..0000000
--- a/video/out/wayland/buffer.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * This file is part of mpv video player.
- * Copyright © 2014 Alexander Preisinger <alexander.preisinger@gmail.com>
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MPLAYER_WAYLAND_BUFFER_H
-#define MPLAYER_WAYLAND_BUFFER_H
-
-#include <libavutil/common.h>
-#include "video/sws_utils.h"
-#include "video/img_format.h"
-#include "video/out/wayland_common.h"
-
-#define SHM_BUFFER_STRIDE(width, bytes) \
- FFALIGN((width) * (bytes), SWS_MIN_BYTE_ALIGN)
-
-typedef struct format {
- enum wl_shm_format wl_format;
- enum mp_imgfmt mp_format;
-} format_t;
-
-int8_t format_get_bytes(const format_t *fmt);
-
-typedef enum shm_buffer_flags {
- SHM_BUFFER_BUSY = 1 << 0, // in use by the compositor
- SHM_BUFFER_DIRTY = 1 << 1, // buffer contains new content
- SHM_BUFFER_ONESHOT = 1 << 2, // free after release
- SHM_BUFFER_RESIZE_LATER = 1 << 3, // free after release
-} shm_buffer_flags_t;
-
-#define SHM_BUFFER_IS_BUSY(b) (!!((b)->flags & SHM_BUFFER_BUSY))
-#define SHM_BUFFER_IS_DIRTY(b) (!!((b)->flags & SHM_BUFFER_DIRTY))
-#define SHM_BUFFER_IS_ONESHOT(b) (!!((b)->flags & SHM_BUFFER_ONESHOT))
-#define SHM_BUFFER_PENDING_RESIZE(b) (!!((b)->flags & SHM_BUFFER_RESIZE_LATER))
-
-#define SHM_BUFFER_SET_BUSY(b) (b)->flags |= SHM_BUFFER_BUSY
-#define SHM_BUFFER_SET_DIRTY(b) (b)->flags |= SHM_BUFFER_DIRTY
-#define SHM_BUFFER_SET_ONESHOT(b) (b)->flags |= SHM_BUFFER_ONESHOT
-#define SHM_BUFFER_SET_PNDNG_RSZ(b) (b)->flags |= SHM_BUFFER_RESIZE_LATER
-
-#define SHM_BUFFER_CLEAR_BUSY(b) (b)->flags &= ~SHM_BUFFER_BUSY
-#define SHM_BUFFER_CLEAR_DIRTY(b) (b)->flags &= ~SHM_BUFFER_DIRTY
-#define SHM_BUFFER_CLEAR_ONESHOT(b) (b)->flags &= ~SHM_BUFFER_ONESHOT
-#define SHM_BUFFER_CLEAR_PNDNG_RSZ(b) (b)->flags &= ~SHM_BUFFER_RESIZE_LATER
-
-typedef struct buffer {
- struct wl_buffer *buffer;
-
- int flags;
-
- uint32_t height;
- uint32_t stride;
- uint32_t bytes; // bytes per pixel
- // width = stride / bytes per pixel
- // size = stride * height
-
- struct wl_shm_pool *shm_pool; // for growing buffers;
-
- int fd;
- void *data;
- uint32_t pool_size; // size of pool and data XXX
- // pool_size can be far bigger than the buffer size
-
- format_t format;
-
- uint32_t pending_height;
- uint32_t pending_width;
-} shm_buffer_t;
-
-shm_buffer_t* shm_buffer_create(uint32_t width,
- uint32_t height,
- format_t fmt,
- struct wl_shm *shm,
- const struct wl_buffer_listener *listener);
-
-// shm pool is only able to grow and won't shrink
-// returns 0 on success or buffer flags indicating the buffer status which
-// prevent it from resizing
-int shm_buffer_resize(shm_buffer_t *buffer, uint32_t width, uint32_t height);
-
-// if shm_buffer_resize returns SHM_BUFFER_BUSY this function can be called
-// after the buffer is released to resize it afterwards
-// returns 0 if no pending resize flag was set and -1 on errors
-int shm_buffer_pending_resize(shm_buffer_t *buffer);
-
-// buffer is freed, don't use the buffer after calling this function on it
-void shm_buffer_destroy(shm_buffer_t *buffer);
-
-#endif /* MPLAYER_WAYLAND_BUFFER_H */
diff --git a/video/out/wayland/memfile.c b/video/out/wayland/memfile.c
deleted file mode 100644
index f28216d..0000000
--- a/video/out/wayland/memfile.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * This file is part of mpv video player.
- * Copyright © 2014 Alexander Preisinger <alexander.preisinger@gmail.com>
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-
-#include "video/out/wayland/memfile.h"
-
-/* copied from weston clients */
-static int set_cloexec_or_close(int fd)
-{
- long flags;
-
- if (fd == -1)
- return -1;
-
- if ((flags = fcntl(fd, F_GETFD)) == -1)
- goto err;
-
- if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
- goto err;
-
- return fd;
-
-err:
- close(fd);
- return -1;
-}
-
-static int create_tmpfile_cloexec(char *tmpname)
-{
- int fd;
-
-#ifdef HAVE_MKOSTEMP
- fd = mkostemp(tmpname, O_CLOEXEC);
- if (fd >= 0)
- unlink(tmpname);
-#else
- fd = mkstemp(tmpname);
- if (fd >= 0) {
- fd = set_cloexec_or_close(fd);
- unlink(tmpname);
- }
-#endif
-
- return fd;
-}
-
-static int os_create_anonymous_file(off_t size)
-{
- static const char template[] = "/mpv-temp-XXXXXX";
- const char *path;
- char *name;
- int fd;
-
- path = getenv("XDG_RUNTIME_DIR");
- if (!path) {
- errno = ENOENT;
- return -1;
- }
-
- name = malloc(strlen(path) + sizeof(template));
- if (!name)
- return -1;
-
- strcpy(name, path);
- strcat(name, template);
-
- fd = create_tmpfile_cloexec(name);
-
- free(name);
-
- if (fd < 0)
- return -1;
-
- if (ftruncate(fd, size) < 0) {
- close(fd);
- return -1;
- }
-
- return fd;
-}
-
-int memfile_create(off_t size)
-{
- return os_create_anonymous_file(size);
-}
diff --git a/video/out/wayland/server-decoration.xml b/video/out/wayland/server-decoration.xml
new file mode 100644
index 0000000..8bc106c
--- /dev/null
+++ b/video/out/wayland/server-decoration.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="server_decoration">
+ <copyright><![CDATA[
+ Copyright (C) 2015 Martin Gräßlin
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ ]]></copyright>
+ <interface name="org_kde_kwin_server_decoration_manager" version="1">
+ <description summary="Server side window decoration manager">
+ This interface allows to coordinate whether the server should create
+ a server-side window decoration around a wl_surface representing a
+ shell surface (wl_shell_surface or similar). By announcing support
+ for this interface the server indicates that it supports server
+ side decorations.
+ </description>
+ <request name="create">
+ <description summary="Create a server-side decoration object for a given surface">
+ When a client creates a server-side decoration object it indicates
+ that it supports the protocol. The client is supposed to tell the
+ server whether it wants server-side decorations or will provide
+ client-side decorations.
+
+ If the client does not create a server-side decoration object for
+ a surface the server interprets this as lack of support for this
+ protocol and considers it as client-side decorated. Nevertheless a
+ client-side decorated surface should use this protocol to indicate
+ to the server that it does not want a server-side deco.
+ </description>
+ <arg name="id" type="new_id" interface="org_kde_kwin_server_decoration"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+ <enum name="mode">
+ <description summary="Possible values to use in request_mode and the event mode."/>
+ <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/>
+ <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/>
+ <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/>
+ </enum>
+ <event name="default_mode">
+ <description summary="The default mode used on the server">
+ This event is emitted directly after binding the interface. It contains
+ the default mode for the decoration. When a new server decoration object
+ is created this new object will be in the default mode until the first
+ request_mode is requested.
+
+ The server may change the default mode at any time.
+ </description>
+ <arg name="mode" type="uint" summary="The default decoration mode applied to newly created server decorations."/>
+ </event>
+ </interface>
+ <interface name="org_kde_kwin_server_decoration" version="1">
+ <request name="release" type="destructor">
+ <description summary="release the server decoration object"/>
+ </request>
+ <enum name="mode">
+ <description summary="Possible values to use in request_mode and the event mode."/>
+ <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/>
+ <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/>
+ <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/>
+ </enum>
+ <request name="request_mode">
+ <description summary="The decoration mode the surface wants to use."/>
+ <arg name="mode" type="uint" summary="The mode this surface wants to use."/>
+ </request>
+ <event name="mode">
+ <description summary="The new decoration mode applied by the server">
+ This event is emitted directly after the decoration is created and
+ represents the base decoration policy by the server. E.g. a server
+ which wants all surfaces to be client-side decorated will send Client,
+ a server which wants server-side decoration will send Server.
+
+ The client can request a different mode through the decoration request.
+ The server will acknowledge this by another event with the same mode. So
+ even if a server prefers server-side decoration it's possible to force a
+ client-side decoration.
+
+ The server may emit this event at any time. In this case the client can
+ again request a different mode. It's the responsibility of the server to
+ prevent a feedback loop.
+ </description>
+ <arg name="mode" type="uint" summary="The decoration mode applied to the surface by the server."/>
+ </event>
+ </interface>
+</protocol>
diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c
index 181723a..19adf01 100644
--- a/video/out/wayland_common.c
+++ b/video/out/wayland_common.c
@@ -1,8 +1,5 @@
/*
* This file is part of mpv video player.
- * Copyright © 2008 Kristian Høgsberg
- * Copyright © 2012-2013 Collabora, Ltd.
- * Copyright © 2013 Alexander Preisinger <alexander.preisinger@gmail.com>
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -18,243 +15,331 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <assert.h>
#include <poll.h>
#include <unistd.h>
-
-#include <sys/mman.h>
#include <linux/input.h>
-
-#include "config.h"
-#include "misc/bstr.h"
-#include "options/options.h"
#include "common/msg.h"
-#include "mpv_talloc.h"
-
-#include "wayland_common.h"
-
-#include "vo.h"
-#include "win_state.h"
+#include "input/input.h"
+#include "input/keycodes.h"
#include "osdep/io.h"
#include "osdep/timer.h"
+#include "win_state.h"
+#include "wayland_common.h"
-#include "input/input.h"
-#include "input/event.h"
-#include "input/keycodes.h"
+// Generated from xdg-shell-unstable-v6.xml
+#include "video/out/wayland/xdg-shell-v6.h"
-static int lookupkey(int key);
+// Generated from idle-inhibit-unstable-v1.xml
+#include "video/out/wayland/idle-inhibit-v1.h"
-static void hide_cursor(struct vo_wayland_state * wl);
-static void show_cursor(struct vo_wayland_state * wl);
-static void window_move(struct vo_wayland_state * wl, uint32_t serial);
-static void window_set_title(struct vo_wayland_state * wl, const char *title);
-static void schedule_resize(struct vo_wayland_state *wl,
- uint32_t edges,
- int32_t width,
- int32_t height);
+// Generated from server-decoration.xml
+#include "video/out/wayland/srv-decor.h"
-static void vo_wayland_fullscreen(struct vo *vo);
+static void xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial)
+{
+ zxdg_shell_v6_pong(shell, serial);
+}
-static const struct wl_callback_listener frame_listener;
+static const struct zxdg_shell_v6_listener xdg_shell_listener = {
+ xdg_shell_ping,
+};
-static const struct mp_keymap keymap[] = {
- // special keys
- {XKB_KEY_Pause, MP_KEY_PAUSE}, {XKB_KEY_Escape, MP_KEY_ESC},
- {XKB_KEY_BackSpace, MP_KEY_BS}, {XKB_KEY_Tab, MP_KEY_TAB},
- {XKB_KEY_Return, MP_KEY_ENTER}, {XKB_KEY_Menu, MP_KEY_MENU},
- {XKB_KEY_Print, MP_KEY_PRINT},
+static int spawn_cursor(struct vo_wayland_state *wl)
+{
+ if (wl->allocated_cursor_scale == wl->scaling) /* Reuse if size is identical */
+ return 0;
+ else if (wl->cursor_theme)
+ wl_cursor_theme_destroy(wl->cursor_theme);
+
+ wl->cursor_theme = wl_cursor_theme_load(NULL, 32*wl->scaling, wl->shm);
+ if (!wl->cursor_theme) {
+ MP_ERR(wl, "Unable to load cursor theme!\n");
+ return 1;
+ }
- // cursor keys
- {XKB_KEY_Left, MP_KEY_LEFT}, {XKB_KEY_Right, MP_KEY_RIGHT},
- {XKB_KEY_Up, MP_KEY_UP}, {XKB_KEY_Down, MP_KEY_DOWN},
+ wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "left_ptr");
+ if (!wl->default_cursor) {
+ MP_ERR(wl, "Unable to load cursor theme!\n");
+ return 1;
+ }
- // navigation block
- {XKB_KEY_Insert, MP_KEY_INSERT}, {XKB_KEY_Delete, MP_KEY_DELETE},
- {XKB_KEY_Home, MP_KEY_HOME}, {XKB_KEY_End, MP_KEY_END},
- {XKB_KEY_Page_Up, MP_KEY_PAGE_UP}, {XKB_KEY_Page_Down, MP_KEY_PAGE_DOWN},
+ wl->allocated_cursor_scale = wl->scaling;
- // F-keys
- {XKB_KEY_F1, MP_KEY_F+1}, {XKB_KEY_F2, MP_KEY_F+2},
- {XKB_KEY_F3, MP_KEY_F+3}, {XKB_KEY_F4, MP_KEY_F+4},
- {XKB_KEY_F5, MP_KEY_F+5}, {XKB_KEY_F6, MP_KEY_F+6},
- {XKB_KEY_F7, MP_KEY_F+7}, {XKB_KEY_F8, MP_KEY_F+8},
- {XKB_KEY_F9, MP_KEY_F+9}, {XKB_KEY_F10, MP_KEY_F+10},
- {XKB_KEY_F11, MP_KEY_F+11}, {XKB_KEY_F12, MP_KEY_F+12},
+ return 0;
+}
- // numpad independent of numlock
- {XKB_KEY_KP_Subtract, '-'}, {XKB_KEY_KP_Add, '+'},
- {XKB_KEY_KP_Multiply, '*'}, {XKB_KEY_KP_Divide, '/'},
- {XKB_KEY_KP_Enter, MP_KEY_KPENTER},
+static int set_cursor_visibility(struct vo_wayland_state *wl, bool on)
+{
+ if (!wl->pointer)
+ return VO_NOTAVAIL;
+ if (on) {
+ if (spawn_cursor(wl))
+ return VO_FALSE;
+ struct wl_cursor_image *img = wl->default_cursor->images[0];
+ struct wl_buffer *buffer = wl_cursor_image_get_buffer(img);
+ if (!buffer)
+ return VO_FALSE;
+ wl_pointer_set_cursor(wl->pointer, wl->pointer_id, wl->cursor_surface,
+ img->hotspot_x/wl->scaling, img->hotspot_y/wl->scaling);
+ wl_surface_set_buffer_scale(wl->cursor_surface, wl->scaling);
+ wl_surface_attach(wl->cursor_surface, buffer, 0, 0);
+ wl_surface_damage(wl->cursor_surface, 0, 0, img->width, img->height);
+ wl_surface_commit(wl->cursor_surface);
+ } else {
+ wl_pointer_set_cursor(wl->pointer, wl->pointer_id, NULL, 0, 0);
+ }
+ return VO_TRUE;
+}
- // numpad with numlock
- {XKB_KEY_KP_0, MP_KEY_KP0}, {XKB_KEY_KP_1, MP_KEY_KP1},
- {XKB_KEY_KP_2, MP_KEY_KP2}, {XKB_KEY_KP_3, MP_KEY_KP3},
- {XKB_KEY_KP_4, MP_KEY_KP4}, {XKB_KEY_KP_5, MP_KEY_KP5},
- {XKB_KEY_KP_6, MP_KEY_KP6}, {XKB_KEY_KP_7, MP_KEY_KP7},
- {XKB_KEY_KP_8, MP_KEY_KP8}, {XKB_KEY_KP_9, MP_KEY_KP9},
- {XKB_KEY_KP_Decimal, MP_KEY_KPDEC}, {XKB_KEY_KP_Separator, MP_KEY_KPDEC},
+static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t sx, wl_fixed_t sy)
+{
+ struct vo_wayland_state *wl = data;
- // numpad without numlock
- {XKB_KEY_KP_Insert, MP_KEY_KPINS}, {XKB_KEY_KP_End, MP_KEY_KP1},
- {XKB_KEY_KP_Down, MP_KEY_KP2}, {XKB_KEY_KP_Page_Down, MP_KEY_KP3},
- {XKB_KEY_KP_Left, MP_KEY_KP4}, {XKB_KEY_KP_Begin, MP_KEY_KP5},
- {XKB_KEY_KP_Right, MP_KEY_KP6}, {XKB_KEY_KP_Home, MP_KEY_KP7},
- {XKB_KEY_KP_Up, MP_KEY_KP8}, {XKB_KEY_KP_Page_Up, MP_KEY_KP9},
- {XKB_KEY_KP_Delete, MP_KEY_KPDEL},
+ wl->pointer = pointer;
+ wl->pointer_id = serial;
- // "Multimedia keyboard" keys
- {XKB_KEY_XF86MenuKB, MP_KEY_MENU},
- {XKB_KEY_XF86AudioPlay, MP_KEY_PLAY}, {XKB_KEY_XF86AudioPause, MP_KEY_PAUSE},
- {XKB_KEY_XF86AudioStop, MP_KEY_STOP},
- {XKB_KEY_XF86AudioPrev, MP_KEY_PREV}, {XKB_KEY_XF86AudioNext, MP_KEY_NEXT},
- {XKB_KEY_XF86AudioRewind, MP_KEY_REWIND},
- {XKB_KEY_XF86AudioForward, MP_KEY_FORWARD},
- {XKB_KEY_XF86AudioMute, MP_KEY_MUTE},
- {XKB_KEY_XF86AudioLowerVolume, MP_KEY_VOLUME_DOWN},
- {XKB_KEY_XF86AudioRaiseVolume, MP_KEY_VOLUME_UP},
- {XKB_KEY_XF86HomePage, MP_KEY_HOMEPAGE}, {XKB_KEY_XF86WWW, MP_KEY_WWW},
- {XKB_KEY_XF86Mail, MP_KEY_MAIL}, {XKB_KEY_XF86Favorites, MP_KEY_FAVORITES},
- {XKB_KEY_XF86Search, MP_KEY_SEARCH}, {XKB_KEY_XF86Sleep, MP_KEY_SLEEP},
+ set_cursor_visibility(wl, true);
+ mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER);
+}
- {0, 0}
-};
+static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
+ uint32_t serial, struct wl_surface *surface)
+{
+ struct vo_wayland_state *wl = data;
+ mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_LEAVE);
+}
+static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
+ uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
+{
+ struct vo_wayland_state *wl = data;
-/** Wayland listeners **/
+ wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling;
+ wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling;
-static void ssurface_handle_ping(void *data,
- struct wl_shell_surface *shell_surface,
- uint32_t serial)
+ mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
+}
+
+static void window_move(struct vo_wayland_state *wl, uint32_t serial)
{
- wl_shell_surface_pong(shell_surface, serial);
+ if (wl->xdg_toplevel)
+ zxdg_toplevel_v6_move(wl->xdg_toplevel, wl->seat, serial);
}
-static void ssurface_handle_configure(void *data,
- struct wl_shell_surface *shell_surface,
- uint32_t edges,
- int32_t width,
- int32_t height)
+static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, uint32_t time, uint32_t button,
+ uint32_t state)
{
struct vo_wayland_state *wl = data;
- float win_aspect = wl->window.aspect;
- if (!width || !height)
- return;
- if (!wl->window.is_fullscreen)
- width = win_aspect * height;
- schedule_resize(wl, edges, width, height);
+
+ state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN
+ : MP_KEY_STATE_UP;
+
+ button = button == BTN_LEFT ? MP_MBTN_LEFT :
+ button == BTN_MIDDLE ? MP_MBTN_MID : MP_MBTN_RIGHT;
+
+ mp_input_put_key(wl->vo->input_ctx, button | state);
+
+ if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) &&
+ (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN))
+ window_move(wl, serial);
}
-static void ssurface_handle_popup_done(void *data,
- struct wl_shell_surface *shell_surface)
+static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis, wl_fixed_t value)
{
+ struct vo_wayland_state *wl = data;
+ double val = wl_fixed_to_double(value)*0.1;
+ switch (axis) {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ if (value > 0)
+ mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN, +val);
+ if (value < 0)
+ mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP, -val);
+ break;
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ if (value > 0)
+ mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT, +val);
+ if (value < 0)
+ mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT, -val);
+ break;
+ }
}
-static const struct wl_shell_surface_listener shell_surface_listener = {
- ssurface_handle_ping,
- ssurface_handle_configure,
- ssurface_handle_popup_done
+static const struct wl_pointer_listener pointer_listener = {
+ pointer_handle_enter,
+ pointer_handle_leave,
+ pointer_handle_motion,
+ pointer_handle_button,
+ pointer_handle_axis,
};
-static void output_handle_geometry(void *data,
- struct wl_output *wl_output,
- int32_t x,
- int32_t y,
- int32_t physical_width,
- int32_t physical_height,
- int32_t subpixel,
- const char *make,
- const char *model,
- int32_t transform)
+static int check_for_resize(struct vo_wayland_state *wl, wl_fixed_t x_w, wl_fixed_t y_w,
+ enum zxdg_toplevel_v6_resize_edge *edge)
{
- struct vo_wayland_output *output = data;
- output->make = make;
- output->model = model;
+ if (wl->touch_entries || wl->fullscreen)
+ return 0;
+
+ const int edge_pixels = 64;
+ int pos[2] = { wl_fixed_to_double(x_w), wl_fixed_to_double(y_w) };
+ int left_edge = pos[0] < edge_pixels;
+ int top_edge = pos[1] < edge_pixels;
+ int right_edge = pos[0] > (mp_rect_w(wl->geometry) - edge_pixels);
+ int bottom_edge = pos[1] > (mp_rect_h(wl->geometry) - edge_pixels);
+
+ if (left_edge) {
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT;
+ if (top_edge)
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT;
+ else if (bottom_edge)
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT;
+ } else if (right_edge) {
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT;
+ if (top_edge)
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT;
+ else if (bottom_edge)
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT;
+ } else if (top_edge) {
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP;
+ } else if (bottom_edge) {
+ *edge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM;
+ } else {
+ *edge = 0;
+ return 0;
+ }
+
+ return 1;
}
-static void output_handle_mode(void *data,
- struct wl_output *wl_output,
- uint32_t flags,
- int32_t width,
- int32_t height,
- int32_t refresh)
+static void touch_handle_down(void *data, struct wl_touch *wl_touch,
+ uint32_t serial, uint32_t time, struct wl_surface *surface,
+ int32_t id, wl_fixed_t x_w, wl_fixed_t y_w)
{
- struct vo_wayland_output *output = data;
+ struct vo_wayland_state *wl = data;
- // only save current mode
- if (!output || !(flags & WL_OUTPUT_MODE_CURRENT))
+ enum zxdg_toplevel_v6_resize_edge edge;
+ if (check_for_resize(wl, x_w, y_w, &edge)) {
+ wl->touch_entries = 0;
+ zxdg_toplevel_v6_resize(wl->xdg_toplevel, wl->seat, serial, edge);
+ return;
+ } else if (wl->touch_entries) {
+ wl->touch_entries = 0;
+ zxdg_toplevel_v6_move(wl->xdg_toplevel, wl->seat, serial);
return;
+ }
- output->width = width;
- output->height = height;
- output->flags = flags;
- output->refresh_rate = refresh;
-}
+ wl->touch_entries = 1;
-static void output_handle_done(void* data, struct wl_output *wl_output)
-{
-}
+ wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
+ wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
-static void output_handle_scale(void* data, struct wl_output *wl_output,
- int32_t factor)
-{
- struct vo_wayland_output *output = data;
- output->scale = factor;
+ mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
+ mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_DOWN);
}
-static const struct wl_output_listener output_listener = {
- output_handle_geometry,
- output_handle_mode,
- output_handle_done,
- output_handle_scale
-};
+static void touch_handle_up(void *data, struct wl_touch *wl_touch,
+ uint32_t serial, uint32_t time, int32_t id)
+{
+ struct vo_wayland_state *wl = data;
+ wl->touch_entries = 0;
-/* SURFACE LISTENER */
+ mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP);
+}
-static void surface_handle_enter(void *data,
- struct wl_surface *wl_surface,
- struct wl_output *output)
+static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
+ uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w)
{
struct vo_wayland_state *wl = data;
- wl->display.current_output = NULL;
- struct vo_wayland_output *o;
- wl_list_for_each(o, &wl->display.output_list, link) {
- if (o->output == output) {
- wl->display.current_output = o;
- break;
- }
- }
+ wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
+ wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
- wl->window.events |= VO_EVENT_WIN_STATE | VO_EVENT_RESIZE;
+ mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
}
-static void surface_handle_leave(void *data,
- struct wl_surface *wl_surface,
- struct wl_output *output)
+static void touch_handle_frame(void *data, struct wl_touch *wl_touch)
{
- // window can be displayed at 2 output, but we only use the most recently
- // entered and discard the previous one even if a part of the window is
- // still visible on the previous entered output.
- // Don't bother with a "leave" logic
}
-static const struct wl_surface_listener surface_listener = {
- surface_handle_enter,
- surface_handle_leave
+static void touch_handle_cancel(void *data, struct wl_touch *wl_touch)
+{
+}
+
+static const struct wl_touch_listener touch_listener = {
+ touch_handle_down,
+ touch_handle_up,
+ touch_handle_motion,
+ touch_handle_frame,
+ touch_handle_cancel,
};
-/* KEYBOARD LISTENER */
-static void keyboard_handle_keymap(void *data,
- struct wl_keyboard *wl_keyboard,
- uint32_t format,
- int32_t fd,
- uint32_t size)
+static const struct mp_keymap keymap[] = {
+ /* Special keys */
+ {XKB_KEY_Pause, MP_KEY_PAUSE}, {XKB_KEY_Escape, MP_KEY_ESC},
+ {XKB_KEY_BackSpace, MP_KEY_BS}, {XKB_KEY_Tab, MP_KEY_TAB},
+ {XKB_KEY_Return, MP_KEY_ENTER}, {XKB_KEY_Menu, MP_KEY_MENU},
+ {XKB_KEY_Print, MP_KEY_PRINT},
+
+ /* Cursor keys */
+ {XKB_KEY_Left, MP_KEY_LEFT}, {XKB_KEY_Right, MP_KEY_RIGHT},
+ {XKB_KEY_Up, MP_KEY_UP}, {XKB_KEY_Down, MP_KEY_DOWN},
+
+ /* Navigation keys */
+ {XKB_KEY_Insert, MP_KEY_INSERT}, {XKB_KEY_Delete, MP_KEY_DELETE},
+ {XKB_KEY_Home, MP_KEY_HOME}, {XKB_KEY_End, MP_KEY_END},
+ {XKB_KEY_Page_Up, MP_KEY_PAGE_UP}, {XKB_KEY_Page_Down, MP_KEY_PAGE_DOWN},
+
+ /* F-keys */
+ {XKB_KEY_F1, MP_KEY_F + 1}, {XKB_KEY_F2, MP_KEY_F + 2},
+ {XKB_KEY_F3, MP_KEY_F + 3}, {XKB_KEY_F4, MP_KEY_F + 4},
+ {XKB_KEY_F5, MP_KEY_F + 5}, {XKB_KEY_F6, MP_KEY_F + 6},
+ {XKB_KEY_F7, MP_KEY_F + 7}, {XKB_KEY_F8, MP_KEY_F + 8},
+ {XKB_KEY_F9, MP_KEY_F + 9}, {XKB_KEY_F10, MP_KEY_F +10},
+ {XKB_KEY_F11, MP_KEY_F +11}, {XKB_KEY_F12, MP_KEY_F +12},
+
+ /* Numpad independent of numlock */
+ {XKB_KEY_KP_Subtract, '-'}, {XKB_KEY_KP_Add, '+'},
+ {XKB_KEY_KP_Multiply, '*'}, {XKB_KEY_KP_Divide, '/'},
+ {XKB_KEY_KP_Enter, MP_KEY_KPENTER},
+
+ /* Numpad with numlock */
+ {XKB_KEY_KP_0, MP_KEY_KP0}, {XKB_KEY_KP_1, MP_KEY_KP1},
+ {XKB_KEY_KP_2, MP_KEY_KP2}, {XKB_KEY_KP_3, MP_KEY_KP3},
+ {XKB_KEY_KP_4, MP_KEY_KP4}, {XKB_KEY_KP_5, MP_KEY_KP5},
+ {XKB_KEY_KP_6, MP_KEY_KP6}, {XKB_KEY_KP_7, MP_KEY_KP7},
+ {XKB_KEY_KP_8, MP_KEY_KP8}, {XKB_KEY_KP_9, MP_KEY_KP9},
+ {XKB_KEY_KP_Decimal, MP_KEY_KPDEC}, {XKB_KEY_KP_Separator, MP_KEY_KPDEC},
+
+ /* Numpad without numlock */
+ {XKB_KEY_KP_Insert, MP_KEY_KPINS}, {XKB_KEY_KP_End, MP_KEY_KP1},
+ {XKB_KEY_KP_Down, MP_KEY_KP2}, {XKB_KEY_KP_Page_Down, MP_KEY_KP3},
+ {XKB_KEY_KP_Left, MP_KEY_KP4}, {XKB_KEY_KP_Begin, MP_KEY_KP5},
+ {XKB_KEY_KP_Right, MP_KEY_KP6}, {XKB_KEY_KP_Home, MP_KEY_KP7},
+ {XKB_KEY_KP_Up, MP_KEY_KP8}, {XKB_KEY_KP_Page_Up, MP_KEY_KP9},
+ {XKB_KEY_KP_Delete, MP_KEY_KPDEL},
+
+ /* Multimedia keys */
+ {XKB_KEY_XF86MenuKB, MP_KEY_MENU},
+ {XKB_KEY_XF86AudioPlay, MP_KEY_PLAY}, {XKB_KEY_XF86AudioPause, MP_KEY_PAUSE},
+ {XKB_KEY_XF86AudioStop, MP_KEY_STOP},
+ {XKB_KEY_XF86AudioPrev, MP_KEY_PREV}, {XKB_KEY_XF86AudioNext, MP_KEY_NEXT},
+ {XKB_KEY_XF86AudioRewind, MP_KEY_REWIND},
+ {XKB_KEY_XF86AudioForward, MP_KEY_FORWARD},
+ {XKB_KEY_XF86AudioMute, MP_KEY_MUTE},
+ {XKB_KEY_XF86AudioLowerVolume, MP_KEY_VOLUME_DOWN},
+ {XKB_KEY_XF86AudioRaiseVolume, MP_KEY_VOLUME_UP},
+ {XKB_KEY_XF86HomePage, MP_KEY_HOMEPAGE}, {XKB_KEY_XF86WWW, MP_KEY_WWW},
+ {XKB_KEY_XF86Mail, MP_KEY_MAIL}, {XKB_KEY_XF86Favorites, MP_KEY_FAVORITES},
+ {XKB_KEY_XF86Search, MP_KEY_SEARCH}, {XKB_KEY_XF86Sleep, MP_KEY_SLEEP},
+
+ {0, 0}
+};
+
+static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size)
{
struct vo_wayland_state *wl = data;
char *map_str;
@@ -270,68 +355,97 @@ static void keyboard_handle_keymap(void *data,
return;
}
- wl->input.xkb.keymap = xkb_keymap_new_from_string(wl->input.xkb.context,
- map_str,
- XKB_KEYMAP_FORMAT_TEXT_V1,
- 0);
+ wl->xkb_keymap = xkb_keymap_new_from_string(wl->xkb_context, map_str,
+ XKB_KEYMAP_FORMAT_TEXT_V1, 0);
munmap(map_str, size);
close(fd);
- if (!wl->input.xkb.keymap) {
+ if (!wl->xkb_keymap) {
MP_ERR(wl, "failed to compile keymap\n");
return;
}
- wl->input.xkb.state = xkb_state_new(wl->input.xkb.keymap);
- if (!wl->input.xkb.state) {
+ wl->xkb_state = xkb_state_new(wl->xkb_keymap);
+ if (!wl->xkb_state) {
MP_ERR(wl, "failed to create XKB state\n");
- xkb_keymap_unref(wl->input.xkb.keymap);
- wl->input.xkb.keymap = NULL;
+ xkb_keymap_unref(wl->xkb_keymap);
+ wl->xkb_keymap = NULL;
return;
}
}
-static void keyboard_handle_enter(void *data,
- struct wl_keyboard *wl_keyboard,
- uint32_t serial,
- struct wl_surface *surface,
+static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface,
struct wl_array *keys)
{
}
-static void keyboard_handle_leave(void *data,
- struct wl_keyboard *wl_keyboard,
- uint32_t serial,
- struct wl_surface *surface)
+static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface)
+{
+}
+
+static bool create_input(struct vo_wayland_state *wl)
{
+ wl->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+
+ if (!wl->xkb_context) {
+ MP_ERR(wl, "failed to initialize input: check xkbcommon\n");
+ return 1;
+ }
+
+ return 0;
}
-static void keyboard_handle_key(void *data,
- struct wl_keyboard *wl_keyboard,
- uint32_t serial,
- uint32_t time,
- uint32_t key,
+static int lookupkey(int key)
+{
+ const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]";
+
+ int mpkey = 0;
+ if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') ||
+ (key >= '0' && key <= '9') ||
+ (key > 0 && key < 256 && strchr(passthrough_keys, key)))
+ mpkey = key;
+
+ if (!mpkey)
+ mpkey = lookup_keymap_table(keymap, key);
+
+ return mpkey;
+}
+
+static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t time, uint32_t key,
uint32_t state)
{
struct vo_wayland_state *wl = data;
uint32_t code = code = key + 8;
- xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->input.xkb.state, code);
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->xkb_state, code);
int mpmod = state == WL_KEYBOARD_KEY_STATE_PRESSED ? MP_KEY_STATE_DOWN
: MP_KEY_STATE_UP;
- static const char *mod_names[] = {XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL,
- XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO, 0};
- static int mods[] = {MP_KEY_MODIFIER_SHIFT, MP_KEY_MODIFIER_CTRL,
- MP_KEY_MODIFIER_ALT, MP_KEY_MODIFIER_META, 0};
+ static const char *mod_names[] = {
+ XKB_MOD_NAME_SHIFT,
+ XKB_MOD_NAME_CTRL,
+ XKB_MOD_NAME_ALT,
+ XKB_MOD_NAME_LOGO,
+ 0,
+ };
+
+ static int mods[] = {
+ MP_KEY_MODIFIER_SHIFT,
+ MP_KEY_MODIFIER_CTRL,
+ MP_KEY_MODIFIER_ALT,
+ MP_KEY_MODIFIER_META,
+ 0,
+ };
for (int n = 0; mods[n]; n++) {
- xkb_mod_index_t index =
- xkb_keymap_mod_get_index(wl->input.xkb.keymap, mod_names[n]);
- if (!xkb_state_mod_index_is_consumed(wl->input.xkb.state, code, index)
- && xkb_state_mod_index_is_active(wl->input.xkb.state, index,
+ xkb_mod_index_t index = xkb_keymap_mod_get_index(wl->xkb_keymap, mod_names[n]);
+ if (!xkb_state_mod_index_is_consumed(wl->xkb_state, code, index)
+ && xkb_state_mod_index_is_active(wl->xkb_state, index,
XKB_STATE_MODS_DEPRESSED))
mpmod |= mods[n];
}
@@ -340,42 +454,29 @@ static void keyboard_handle_key(void *data,
if (mpkey) {
mp_input_put_key(wl->vo->input_ctx, mpkey | mpmod);
} else {
- char s[80];
+ char s[128];
if (xkb_keysym_to_utf8(sym, s, sizeof(s)) > 0)
mp_input_put_key_utf8(wl->vo->input_ctx, mpmod, bstr0(s));
}
}
-static void keyboard_handle_modifiers(void *data,
- struct wl_keyboard *wl_keyboard,
- uint32_t serial,
- uint32_t mods_depressed,
- uint32_t mods_latched,
- uint32_t mods_locked,
+static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t mods_depressed,
+ uint32_t mods_latched, uint32_t mods_locked,
uint32_t group)
{
struct vo_wayland_state *wl = data;
- xkb_state_update_mask(wl->input.xkb.state,
- mods_depressed,
- mods_latched,
- mods_locked,
- 0, 0, group);
+ xkb_state_update_mask(wl->xkb_state, mods_depressed, mods_latched,
+ mods_locked, 0, 0, group);
}
-static void keyboard_handle_repeat_info(void *data,
- struct wl_keyboard *wl_keyboard,
- int32_t rate,
- int32_t delay)
+static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay)
{
struct vo_wayland_state *wl = data;
- if (wl->vo->opts->native_keyrepeat) {
- if (rate < 0 || delay < 0) {
- MP_WARN(wl, "Invalid rate or delay values sent by compositor\n");
- return;
- }
+ if (wl->vo->opts->native_keyrepeat)
mp_input_set_repeat_info(wl->vo->input_ctx, rate, delay);
- }
}
static const struct wl_keyboard_listener keyboard_listener = {
@@ -384,562 +485,566 @@ static const struct wl_keyboard_listener keyboard_listener = {
keyboard_handle_leave,
keyboard_handle_key,
keyboard_handle_modifiers,
- keyboard_handle_repeat_info
+ keyboard_handle_repeat_info,
};
-/* POINTER LISTENER */
-static void pointer_handle_enter(void *data,
- struct wl_pointer *pointer,
- uint32_t serial,
- struct wl_surface *surface,
- wl_fixed_t sx_w,
- wl_fixed_t sy_w)
+static void seat_handle_caps(void *data, struct wl_seat *seat,
+ enum wl_seat_capability caps)
{
struct vo_wayland_state *wl = data;
- wl->cursor.serial = serial;
- wl->cursor.pointer = pointer;
+ if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->pointer) {
+ wl->pointer = wl_seat_get_pointer(seat);
+ wl_pointer_add_listener(wl->pointer, &pointer_listener, wl);
+ } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wl->pointer) {
+ wl_pointer_destroy(wl->pointer);
+ wl->pointer = NULL;
+ }
- /* Release the left button on pointer enter again
- * because after moving the shell surface no release event is sent */
- mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER);
- mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP);
- show_cursor(wl);
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !wl->keyboard) {
+ wl->keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(wl->keyboard, &keyboard_listener, wl);
+ } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && wl->keyboard) {
+ wl_keyboard_destroy(wl->keyboard);
+ wl->keyboard = NULL;
+ }
+
+ if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !wl->touch) {
+ wl->touch = wl_seat_get_touch(seat);
+ wl_touch_set_user_data(wl->touch, wl);
+ wl_touch_add_listener(wl->touch, &touch_listener, wl);
+ } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && wl->touch) {
+ wl_touch_destroy(wl->touch);
+ wl->touch = NULL;
+ }
}
-static void pointer_handle_leave(void *data,
- struct wl_pointer *pointer,
- uint32_t serial,
- struct wl_surface *surface)
+static const struct wl_seat_listener seat_listener = {
+ seat_handle_caps,
+};
+
+static void output_handle_geometry(void *data, struct wl_output *wl_output,
+ int32_t x, int32_t y, int32_t phys_width,
+ int32_t phys_height, int32_t subpixel,
+ const char *make, const char *model,
+ int32_t transform)
{
- struct vo_wayland_state *wl = data;
- mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_LEAVE);
+ struct vo_wayland_output *output = data;
+ output->make = talloc_strdup(output->wl, make);
+ output->model = talloc_strdup(output->wl, model);
+ output->geometry.x0 = x;
+ output->geometry.y0 = y;
+ output->phys_width = phys_width;
+ output->phys_height = phys_height;
}
-static void pointer_handle_motion(void *data,
- struct wl_pointer *pointer,
- uint32_t time,
- wl_fixed_t sx_w,
- wl_fixed_t sy_w)
+static void output_handle_mode(void *data, struct wl_output *wl_output,
+ uint32_t flags, int32_t width,
+ int32_t height, int32_t refresh)
{
- int32_t scale = 1;
- struct vo_wayland_state *wl = data;
-
- if (wl->display.current_output)
- scale = wl->display.current_output->scale;
+ struct vo_wayland_output *output = data;
- wl->cursor.pointer = pointer;
- wl->window.mouse_x = scale*wl_fixed_to_int(sx_w);
- wl->window.mouse_y = scale*wl_fixed_to_int(sy_w);
+ /* Only save current mode */
+ if (!(flags & WL_OUTPUT_MODE_CURRENT))
+ return;
- mp_input_set_mouse_pos(wl->vo->input_ctx, wl->window.mouse_x,
- wl->window.mouse_y);
+ output->geometry.x1 = width;
+ output->geometry.y1 = height;
+ output->flags = flags;
+ output->refresh_rate = (double)refresh * 0.001;
}
-static void pointer_handle_button(void *data,
- struct wl_pointer *pointer,
- uint32_t serial,
- uint32_t time,
- uint32_t button,
- uint32_t state)
+static void output_handle_done(void* data, struct wl_output *wl_output)
{
- struct vo_wayland_state *wl = data;
-
- state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN
- : MP_KEY_STATE_UP;
-
- button = button == BTN_LEFT ? MP_MBTN_LEFT :
- button == BTN_MIDDLE ? MP_MBTN_MID : MP_MBTN_RIGHT;
-
- mp_input_put_key(wl->vo->input_ctx, button | state);
-
- if (!mp_input_test_dragging(wl->vo->input_ctx, wl->window.mouse_x, wl->window.mouse_y) &&
- (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN))
- window_move(wl, serial);
+ struct vo_wayland_output *o = data;
+
+ o->geometry.x1 += o->geometry.x0;
+ o->geometry.y1 += o->geometry.y0;
+
+ MP_VERBOSE(o->wl, "Registered output %s %s (0x%x):\n"
+ "\tx: %dpx, y: %dpx\n"
+ "\tw: %dpx (%dmm), h: %dpx (%dmm)\n"
+ "\tscale: %d\n"
+ "\tHz: %f\n", o->make, o->model, o->id, o->geometry.x0,
+ o->geometry.y0, mp_rect_w(o->geometry), o->phys_width,
+ mp_rect_h(o->geometry), o->phys_height, o->scale, o->refresh_rate);
}
-static void pointer_handle_axis(void *data,
- struct wl_pointer *pointer,
- uint32_t time,
- uint32_t axis,
- wl_fixed_t value)
+static void output_handle_scale(void* data, struct wl_output *wl_output,
+ int32_t factor)
{
- struct vo_wayland_state *wl = data;
-
- // value is 10.00 on a normal mouse wheel
- // scale it down to 1.00 for multipliying it with the commands
- if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
- if (value > 0)
- mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN,
- wl_fixed_to_double(value)*0.1);
- if (value < 0)
- mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP,
- wl_fixed_to_double(value)*-0.1);
- }
- else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
- if (value > 0)
- mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT,
- wl_fixed_to_double(value)*0.1);
- if (value < 0)
- mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT,
- wl_fixed_to_double(value)*-0.1);
+ struct vo_wayland_output *output = data;
+ if (!factor) {
+ MP_ERR(output->wl, "Invalid output scale given by the compositor!\n");
+ return;
}
+ output->scale = factor;
}
-static const struct wl_pointer_listener pointer_listener = {
- pointer_handle_enter,
- pointer_handle_leave,
- pointer_handle_motion,
- pointer_handle_button,
- pointer_handle_axis,
+static const struct wl_output_listener output_listener = {
+ output_handle_geometry,
+ output_handle_mode,
+ output_handle_done,
+ output_handle_scale,
};
-static void seat_handle_capabilities(void *data,
- struct wl_seat *seat,
- enum wl_seat_capability caps)
+static void data_offer_handle_offer(void *data, struct wl_data_offer *offer,
+ const char *mime_type)
{
struct vo_wayland_state *wl = data;
-
- if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !wl->input.keyboard) {
- wl->input.keyboard = wl_seat_get_keyboard(seat);
- wl_keyboard_add_listener(wl->input.keyboard, &keyboard_listener, wl);
- }
- else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && wl->input.keyboard) {
- wl_keyboard_destroy(wl->input.keyboard);
- wl->input.keyboard = NULL;
- }
- if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->input.pointer) {
- wl->input.pointer = wl_seat_get_pointer(seat);
- wl_pointer_add_listener(wl->input.pointer, &pointer_listener, wl);
- }
- else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wl->input.pointer) {
- wl_pointer_destroy(wl->input.pointer);
- wl->input.pointer = NULL;
+ int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type);
+ if (score > wl->dnd_mime_score) {
+ wl->dnd_mime_score = score;
+ talloc_free(wl->dnd_mime_type);
+ wl->dnd_mime_type = talloc_strdup(wl, mime_type);
+ MP_VERBOSE(wl, "Given DND offer with mime type %s\n", wl->dnd_mime_type);
}
}
-static void seat_handle_name(void *data,
- struct wl_seat *seat,
- const char *name)
+static void data_offer_source_actions(void *data, struct wl_data_offer *offer, uint32_t source_actions)
+{
+
+}
+
+static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
{
struct vo_wayland_state *wl = data;
- MP_VERBOSE(wl, "Seat \"%s\" connected\n", name);
+ wl->dnd_action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ?
+ DND_REPLACE : DND_APPEND;
+ MP_VERBOSE(wl, "DND action is %s\n",
+ wl->dnd_action == DND_REPLACE ? "DND_REPLACE" : "DND_APPEND");
}
-static const struct wl_seat_listener seat_listener = {
- seat_handle_capabilities,
- seat_handle_name,
+static const struct wl_data_offer_listener data_offer_listener = {
+ data_offer_handle_offer,
+ data_offer_source_actions,
+ data_offer_action,
};
-static void registry_handle_global(void *data, struct wl_registry *reg,
- uint32_t id, const char *interface,
- uint32_t version)
+static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ddev,
+ struct wl_data_offer *id)
{
struct vo_wayland_state *wl = data;
+ if (wl->dnd_offer)
+ wl_data_offer_destroy(wl->dnd_offer);
- if (strcmp(interface, "wl_compositor") == 0) {
-
- wl->display.compositor = wl_registry_bind(reg, id,
- &wl_compositor_interface,
- MPMIN(3, version));
- }
-
- else if (strcmp(interface, "wl_shell") == 0) {
-
- wl->display.shell = wl_registry_bind(reg, id, &wl_shell_interface, 1);
- }
-
- else if (strcmp(interface, "wl_shm") == 0) {
-
- wl->display.shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
- }
-
- else if (strcmp(interface, "wl_output") == 0) {
-
- struct vo_wayland_output *output =
- talloc_zero(wl, struct vo_wayland_output);
-
- output->id = id;
- output->scale = 1;
- output->output = wl_registry_bind(reg, id, &wl_output_interface,
- MPMIN(2, version));
+ wl->dnd_offer = id;
+ wl_data_offer_add_listener(id, &data_offer_listener, wl);
+}
- wl_output_add_listener(output->output, &output_listener, output);
- wl_list_insert(&wl->display.output_list, &output->link);
+static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t x, wl_fixed_t y,
+ struct wl_data_offer *id)
+{
+ struct vo_wayland_state *wl = data;
+ if (wl->dnd_offer != id) {
+ MP_FATAL(wl, "DND offer ID mismatch!\n");
+ return;
}
- else if (strcmp(interface, "wl_seat") == 0) {
-
- wl->input.seat = wl_registry_bind(reg, id, &wl_seat_interface, 4);
- wl_seat_add_listener(wl->input.seat, &seat_listener, wl);
-
- }
+ wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
- else if (strcmp(interface, "wl_subcompositor") == 0) {
+ wl_data_offer_accept(id, serial, wl->dnd_mime_type);
- wl->display.subcomp = wl_registry_bind(reg, id,
- &wl_subcompositor_interface, 1);
- }
+ MP_VERBOSE(wl, "Accepting DND offer with mime type %s\n", wl->dnd_mime_type);
}
-static void registry_handle_global_remove(void *data,
- struct wl_registry *registry,
- uint32_t id)
+static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev)
{
-}
+ struct vo_wayland_state *wl = data;
-static const struct wl_registry_listener registry_listener = {
- registry_handle_global,
- registry_handle_global_remove
-};
+ if (wl->dnd_offer) {
+ if (wl->dnd_fd != -1)
+ return;
+ wl_data_offer_destroy(wl->dnd_offer);
+ wl->dnd_offer = NULL;
+ }
+ MP_VERBOSE(wl, "Releasing DND offer with mime type %s\n", wl->dnd_mime_type);
-/*** internal functions ***/
+ talloc_free(wl->dnd_mime_type);
+ wl->dnd_mime_type = NULL;
+ wl->dnd_mime_score = 0;
+}
-static int lookupkey(int key)
+static void data_device_handle_motion(void *data, struct wl_data_device *wl_ddev,
+ uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
- const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]";
-
- int mpkey = 0;
- if ((key >= 'a' && key <= 'z') ||
- (key >= 'A' && key <= 'Z') ||
- (key >= '0' && key <= '9') ||
- (key > 0 && key < 256 && strchr(passthrough_keys, key)))
- mpkey = key;
-
- if (!mpkey)
- mpkey = lookup_keymap_table(keymap, key);
+ struct vo_wayland_state *wl = data;
- return mpkey;
+ wl_data_offer_accept(wl->dnd_offer, time, wl->dnd_mime_type);
}
-static void hide_cursor (struct vo_wayland_state *wl)
+static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev)
{
- if (!wl->cursor.pointer)
- return;
+ struct vo_wayland_state *wl = data;
- wl_pointer_set_cursor(wl->cursor.pointer, wl->cursor.serial, NULL, 0, 0);
-}
+ int pipefd[2];
-static void show_cursor (struct vo_wayland_state *wl)
-{
- if (!wl->cursor.pointer)
+ if (pipe2(pipefd, O_CLOEXEC) == -1) {
+ MP_ERR(wl, "Failed to create dnd pipe!\n");
return;
+ }
- struct wl_cursor_image *image = wl->cursor.default_cursor->images[0];
- struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
-
- wl_pointer_set_cursor(wl->cursor.pointer,
- wl->cursor.serial,
- wl->cursor.surface,
- image->hotspot_x,
- image->hotspot_y);
+ MP_VERBOSE(wl, "Receiving DND offer with mime %s\n", wl->dnd_mime_type);
- wl_surface_attach(wl->cursor.surface, buffer, 0, 0);
- wl_surface_damage(wl->cursor.surface, 0, 0, image->width, image->height);
- wl_surface_commit(wl->cursor.surface);
-}
+ wl_data_offer_receive(wl->dnd_offer, wl->dnd_mime_type, pipefd[1]);
+ close(pipefd[1]);
-static void window_move(struct vo_wayland_state *wl, uint32_t serial)
-{
- if (wl->display.shell)
- wl_shell_surface_move(wl->window.shell_surface, wl->input.seat, serial);
+ wl->dnd_fd = pipefd[0];
}
-static void window_set_toplevel(struct vo_wayland_state *wl)
+static void data_device_handle_selection(void *data, struct wl_data_device *wl_ddev,
+ struct wl_data_offer *id)
{
- if (wl->display.shell)
- wl_shell_surface_set_toplevel(wl->window.shell_surface);
}
-static void window_set_title(struct vo_wayland_state *wl, const char *title)
-{
- if (wl->display.shell)
- wl_shell_surface_set_title(wl->window.shell_surface, title);
-}
+static const struct wl_data_device_listener data_device_listener = {
+ data_device_handle_data_offer,
+ data_device_handle_enter,
+ data_device_handle_leave,
+ data_device_handle_motion,
+ data_device_handle_drop,
+ data_device_handle_selection,
+};
-static void schedule_resize(struct vo_wayland_state *wl,
- uint32_t edges,
- int32_t width,
- int32_t height)
+static void surface_handle_enter(void *data, struct wl_surface *wl_surface,
+ struct wl_output *output)
{
- int32_t minimum_size = 150;
- int32_t x, y;
- float win_aspect = wl->window.aspect;
- if (win_aspect <= 0)
- win_aspect = 1;
-
- MP_DBG(wl, "schedule resize: %dx%d\n", width, height);
-
- width = MPMAX(minimum_size, width);
- height = MPMAX(minimum_size, height);
- if (wl->display.current_output) {
- int scale = wl->display.current_output->scale;
- width = MPMIN(width, wl->display.current_output->width /scale);
- height = MPMIN(height, wl->display.current_output->height/scale);
- }
+ struct vo_wayland_state *wl = data;
+ wl->current_output = NULL;
- // don't keep the aspect ratio in fullscreen mode because the compositor
- // shows the desktop in the border regions if the video does not have the same
- // aspect ratio as the screen
- /* if only the height is changed we have to calculate the width
- * in any other case we calculate the height */
- switch (edges) {
- case WL_SHELL_SURFACE_RESIZE_TOP:
- case WL_SHELL_SURFACE_RESIZE_BOTTOM:
- width = win_aspect * height;
- break;
- case WL_SHELL_SURFACE_RESIZE_LEFT:
- case WL_SHELL_SURFACE_RESIZE_RIGHT:
- case WL_SHELL_SURFACE_RESIZE_TOP_LEFT: // just a preference
- case WL_SHELL_SURFACE_RESIZE_TOP_RIGHT:
- case WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT:
- case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT:
- height = (1 / win_aspect) * width;
+ struct vo_wayland_output *o;
+ wl_list_for_each(o, &wl->output_list, link) {
+ if (o->output == output) {
+ wl->current_output = o;
break;
+ }
}
- if (edges & WL_SHELL_SURFACE_RESIZE_LEFT)
- x = wl->window.width - width;
- else
- x = 0;
+ wl->current_output->has_surface = true;
+ if (wl->scaling != wl->current_output->scale)
+ wl->pending_vo_events |= VO_EVENT_RESIZE;
+ wl->scaling = wl->current_output->scale;
- if (edges & WL_SHELL_SURFACE_RESIZE_TOP)
- y = wl->window.height - height;
- else
- y = 0;
+ MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %i\n", o->make,
+ o->model, o->id, wl->scaling);
- wl->window.sh_width = width;
- wl->window.sh_height = height;
- wl->window.sh_x = x;
- wl->window.sh_y = y;
- wl->window.events |= VO_EVENT_RESIZE;
+ wl->pending_vo_events |= VO_EVENT_WIN_STATE;
}
-static void frame_callback(void *data,
- struct wl_callback *callback,
- uint32_t time)
+static void surface_handle_leave(void *data, struct wl_surface *wl_surface,
+ struct wl_output *output)
{
struct vo_wayland_state *wl = data;
- if (wl->frame.function)
- wl->frame.function(wl->frame.data, time);
+ struct vo_wayland_output *o;
+ wl_list_for_each(o, &wl->output_list, link) {
+ if (o->output == output) {
+ o->has_surface = false;
+ wl->pending_vo_events |= VO_EVENT_WIN_STATE;
+ return;
+ }
+ }
+}
+
+static const struct wl_surface_listener surface_listener = {
+ surface_handle_enter,
+ surface_handle_leave,
+};
+
+static const struct wl_callback_listener frame_listener;
+
+static void frame_callback(void *data, struct wl_callback *callback, uint32_t time)
+{
+ struct vo_wayland_state *wl = data;
if (callback)
wl_callback_destroy(callback);
- wl->frame.callback = wl_surface_frame(wl->window.video_surface);
-
- if (!wl->frame.callback) {
- MP_ERR(wl, "wl_surface_frame failed\n");
- return;
- }
+ wl->frame_callback = wl_surface_frame(wl->surface);
+ wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
- wl_callback_add_listener(wl->frame.callback, &frame_listener, wl);
- wl_surface_commit(wl->window.video_surface);
+ if (!vo_render_frame_external(wl->vo))
+ wl_surface_commit(wl->surface);
}
static const struct wl_callback_listener frame_listener = {
- frame_callback
+ frame_callback,
};
-static bool create_display(struct vo_wayland_state *wl)
+static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id,
+ const char *interface, uint32_t ver)
{
- if (wl->vo->probing && !getenv("XDG_RUNTIME_DIR"))
- return false;
-
- wl->display.display = wl_display_connect(NULL);
-
- if (!wl->display.display) {
- MP_MSG(wl, wl->vo->probing ? MSGL_V : MSGL_ERR,
- "failed to connect to a wayland server: "
- "check if a wayland compositor is running\n");
+ int found = 1;
+ struct vo_wayland_state *wl = data;
- return false;
+ if (!strcmp(interface, wl_compositor_interface.name) && found++) {
+ ver = MPMIN(ver, 4); /* Cap the version */
+ wl->compositor = wl_registry_bind(reg, id, &wl_compositor_interface, ver);
+ wl->surface = wl_compositor_create_surface(wl->compositor);
+ wl->cursor_surface = wl_compositor_create_surface(wl->compositor);
+ wl_surface_add_listener(wl->surface, &surface_listener, wl);
+ vo_enable_external_renderloop(wl->vo);
+ wl->frame_callback = wl_surface_frame(wl->surface);
+ wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
}
- wl->display.registry = wl_display_get_registry(wl->display.display);
- wl_registry_add_listener(wl->display.registry, &registry_listener, wl);
-
- wl_display_roundtrip(wl->display.display);
+ if (!strcmp(interface, wl_output_interface.name) && (ver >= 2) && found++) {
+ struct vo_wayland_output *output = talloc_zero(wl, struct vo_wayland_output);
- wl->display.display_fd = wl_display_get_fd(wl->display.display);
+ output->wl = wl;
+ output->id = id;
+ output->scale = 1;
+ output->output = wl_registry_bind(reg, id, &wl_output_interface, 2);
- return true;
-}
-
-static void destroy_display(struct vo_wayland_state *wl)
-{
- struct vo_wayland_output *output = NULL;
- struct vo_wayland_output *tmp = NULL;
-
- wl_list_for_each_safe(output, tmp, &wl->display.output_list, link) {
- if (output && output->output) {
- wl_output_destroy(output->output);
- output->output = NULL;
- wl_list_remove(&output->link);
- }
+ wl_output_add_listener(output->output, &output_listener, output);
+ wl_list_insert(&wl->output_list, &output->link);
}
- if (wl->display.shm)
- wl_shm_destroy(wl->display.shm);
+ if (!strcmp(interface, zxdg_shell_v6_interface.name) && found++) {
+ wl->shell = wl_registry_bind(reg, id, &zxdg_shell_v6_interface, 1);
+ zxdg_shell_v6_add_listener(wl->shell, &xdg_shell_listener, wl);
+ }
- if (wl->display.shell)
- wl_shell_destroy(wl->display.shell);
+ if (!strcmp(interface, wl_seat_interface.name) && found++) {
+ wl->seat = wl_registry_bind(reg, id, &wl_seat_interface, 1);
+ wl_seat_add_listener(wl->seat, &seat_listener, wl);
+ }
- if (wl->display.subcomp)
- wl_subcompositor_destroy(wl->display.subcomp);
+ if (!strcmp(interface, wl_shm_interface.name) && found++) {
+ wl->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
+ }
- if (wl->display.compositor)
- wl_compositor_destroy(wl->display.compositor);
+ if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) {
+ wl->dnd_devman = wl_registry_bind(reg, id, &wl_data_device_manager_interface, 3);
+ }
- if (wl->display.registry)
- wl_registry_destroy(wl->display.registry);
+ if (!strcmp(interface, org_kde_kwin_server_decoration_manager_interface.name) && found++) {
+ wl->server_decoration_manager = wl_registry_bind(reg, id, &org_kde_kwin_server_decoration_manager_interface, 1);
+ }
- if (wl->display.display) {
- wl_display_flush(wl->display.display);
- wl_display_disconnect(wl->display.display);
+ if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) {
+ wl->idle_inhibit_manager = wl_registry_bind(reg, id, &zwp_idle_inhibit_manager_v1_interface, 1);
}
+
+ if (found > 1)
+ MP_VERBOSE(wl, "Registered for protocol %s\n", interface);
}
-static bool create_window(struct vo_wayland_state *wl)
+static void remove_output(struct vo_wayland_output *out)
{
- wl->window.video_surface =
- wl_compositor_create_surface(wl->display.compositor);
-
- wl_surface_add_listener(wl->window.video_surface,
- &surface_listener, wl);
+ if (!out)
+ return;
- if (wl->display.shell) {
- wl->window.shell_surface = wl_shell_get_shell_surface(wl->display.shell,
- wl->window.video_surface);
+ MP_VERBOSE(out->wl, "Deregistering output %s %s (0x%x)\n", out->make,
+ out->model, out->id);
+ wl_list_remove(&out->link);
+ talloc_free(out->make);
+ talloc_free(out->model);
+ talloc_free(out);
+ return;
+}
- if (!wl->window.shell_surface) {
- MP_ERR(wl, "creating shell surface failed\n");
- return false;
+static void registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id)
+{
+ struct vo_wayland_state *wl = data;
+ struct vo_wayland_output *output, *tmp;
+ wl_list_for_each_safe(output, tmp, &wl->output_list, link) {
+ if (output->id == id) {
+ remove_output(output);
+ return;
}
-
- wl_shell_surface_add_listener(wl->window.shell_surface,
- &shell_surface_listener, wl);
-
- wl_shell_surface_set_toplevel(wl->window.shell_surface);
- wl_shell_surface_set_class(wl->window.shell_surface, "mpv");
}
-
- return true;
}
-static void destroy_window(struct vo_wayland_state *wl)
-{
- if (wl->window.shell_surface)
- wl_shell_surface_destroy(wl->window.shell_surface);
-
- if (wl->window.video_surface)
- wl_surface_destroy(wl->window.video_surface);
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_add,
+ registry_handle_remove,
+};
- if (wl->frame.callback)
- wl_callback_destroy(wl->frame.callback);
+static void handle_surface_config(void *data, struct zxdg_surface_v6 *surface,
+ uint32_t serial)
+{
+ zxdg_surface_v6_ack_configure(surface, serial);
}
-static bool create_cursor(struct vo_wayland_state *wl)
+static const struct zxdg_surface_v6_listener xdg_surface_listener = {
+ handle_surface_config,
+};
+
+static void handle_toplevel_config(void *data, struct zxdg_toplevel_v6 *toplevel,
+ int32_t width, int32_t height, struct wl_array *states)
{
- if (!wl->display.shm) {
- MP_ERR(wl->vo, "no shm interface available\n");
- return false;
+ struct vo_wayland_state *wl = data;
+ struct mp_rect old_geometry = wl->geometry;
+
+ int prev_fs_state = wl->fullscreen;
+ bool maximized = false;
+ wl->fullscreen = false;
+ enum zxdg_toplevel_v6_state *state;
+ wl_array_for_each(state, states) {
+ switch (*state) {
+ case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
+ wl->fullscreen = true;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
+ wl->pending_vo_events |= VO_EVENT_LIVE_RESIZING;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
+ maximized = true;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
+ break;
+ }
}
- wl->cursor.surface =
- wl_compositor_create_surface(wl->display.compositor);
+ if (prev_fs_state != wl->fullscreen)
+ wl->pending_vo_events |= VO_EVENT_FULLSCREEN_STATE;
+ if (!(wl->pending_vo_events & VO_EVENT_LIVE_RESIZING))
+ vo_query_and_reset_events(wl->vo, VO_EVENT_LIVE_RESIZING);
+
+ if (width > 0 && height > 0) {
+ if (!wl->fullscreen) {
+ if (wl->vo->opts->keepaspect && wl->vo->opts->keepaspect_window &&
+ !maximized) {
+ if (width > height)
+ width = height * wl->aspect_ratio;
+ else
+ height = width / wl->aspect_ratio;
+ }
+ wl->window_size.x0 = 0;
+ wl->window_size.y0 = 0;
+ wl->window_size.x1 = width;
+ wl->window_size.y1 = height;
+ }
+ wl->geometry.x0 = 0;
+ wl->geometry.y0 = 0;
+ wl->geometry.x1 = width;
+ wl->geometry.y1 = height;
+ } else {
+ wl->geometry = wl->window_size;
+ }
- if (!wl->cursor.surface)
- return false;
+ if (mp_rect_equals(&old_geometry, &wl->geometry))
+ return;
- wl->cursor.theme = wl_cursor_theme_load(NULL, 32, wl->display.shm);
- wl->cursor.default_cursor = wl_cursor_theme_get_cursor(wl->cursor.theme,
- "left_ptr");
+ MP_VERBOSE(wl, "Resizing due to xdg from %ix%i to %ix%i\n",
+ mp_rect_w(old_geometry)*wl->scaling, mp_rect_h(old_geometry)*wl->scaling,
+ mp_rect_w(wl->geometry)*wl->scaling, mp_rect_h(wl->geometry)*wl->scaling);
- return true;
+ wl->pending_vo_events |= VO_EVENT_RESIZE;
}
-static void destroy_cursor(struct vo_wayland_state *wl)
+static void handle_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel)
{
- if (wl->cursor.theme)
- wl_cursor_theme_destroy(wl->cursor.theme);
-
- if (wl->cursor.surface)
- wl_surface_destroy(wl->cursor.surface);
+ struct vo_wayland_state *wl = data;
+ mp_input_put_key(wl->vo->input_ctx, MP_KEY_CLOSE_WIN);
}
-static bool create_input(struct vo_wayland_state *wl)
+static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = {
+ handle_toplevel_config,
+ handle_toplevel_close,
+};
+
+static int create_xdg_surface(struct vo_wayland_state *wl)
{
- wl->input.xkb.context = xkb_context_new(0);
+ wl->xdg_surface = zxdg_shell_v6_get_xdg_surface(wl->shell, wl->surface);
+ zxdg_surface_v6_add_listener(wl->xdg_surface, &xdg_surface_listener, wl);
- if (!wl->input.xkb.context) {
- MP_ERR(wl, "failed to initialize input: check xkbcommon\n");
- return false;
- }
+ wl->xdg_toplevel = zxdg_surface_v6_get_toplevel(wl->xdg_surface);
+ zxdg_toplevel_v6_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, wl);
- return true;
+ zxdg_toplevel_v6_set_title (wl->xdg_toplevel, "mpv");
+ zxdg_toplevel_v6_set_app_id(wl->xdg_toplevel, "mpv");
+
+ return 0;
}
-static void destroy_input(struct vo_wayland_state *wl)
+static int set_border_decorations(struct vo_wayland_state *wl, int state)
{
- if (wl->input.keyboard) {
- wl_keyboard_destroy(wl->input.keyboard);
- xkb_keymap_unref(wl->input.xkb.keymap);
- xkb_state_unref(wl->input.xkb.state);
+ if (!wl->server_decoration)
+ return VO_NOTIMPL;
+ enum org_kde_kwin_server_decoration_mode mode;
+ if (state) {
+ MP_VERBOSE(wl, "Enabling server decorations\n");
+ mode = ORG_KDE_KWIN_SERVER_DECORATION_MODE_SERVER;
+ } else {
+ MP_VERBOSE(wl, "Disabling server decorations\n");
+ mode = ORG_KDE_KWIN_SERVER_DECORATION_MODE_NONE;
}
-
- if (wl->input.xkb.context)
- xkb_context_unref(wl->input.xkb.context);
-
- if (wl->input.pointer)
- wl_pointer_destroy(wl->input.pointer);
-
- if (wl->input.seat)
- wl_seat_destroy(wl->input.seat);
+ org_kde_kwin_server_decoration_request_mode(wl->server_decoration, mode);
+ return VO_TRUE;
}
-/*** mplayer2 interface ***/
-
int vo_wayland_init(struct vo *vo)
{
- vo->wayland = talloc_zero(NULL, struct vo_wayland_state);
- struct vo_wayland_state *wl = vo->wayland;
- *wl = (struct vo_wayland_state){
+ vo->wl = talloc_zero(NULL, struct vo_wayland_state);
+ struct vo_wayland_state *wl = vo->wl;
+
+ *wl = (struct vo_wayland_state) {
+ .display = wl_display_connect(NULL),
.vo = vo,
.log = mp_log_new(wl, vo->log, "wayland"),
+ .scaling = 1,
.wakeup_pipe = {-1, -1},
+ .dnd_fd = -1,
};
- wl_list_init(&wl->display.output_list);
+ wl_list_init(&wl->output_list);
+
+ if (!wl->display)
+ return false;
+
+ if (create_input(wl))
+ return false;
+
+ wl->registry = wl_display_get_registry(wl->display);
+ wl_registry_add_listener(wl->registry, &registry_listener, wl);
+
+ /* Do a roundtrip to run the registry */
+ wl_display_roundtrip(wl->display);
+
+ if (!wl->shell) {
+ MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n",
+ zxdg_shell_v6_interface.name);
+ return false;
+ }
+
+ if (!wl_list_length(&wl->output_list)) {
+ MP_FATAL(wl, "No outputs found or compositor doesn't support %s (ver. 2)\n",
+ wl_output_interface.name);
+ return false;
+ }
- if (!create_input(wl)
- || !create_display(wl)
- || !create_window(wl)
- || !create_cursor(wl))
- {
- vo_wayland_uninit(vo);
+ /* Can't be initialized during registry due to multi-protocol dependence */
+ if (create_xdg_surface(wl))
return false;
+
+ if (wl->dnd_devman) {
+ wl->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, wl->seat);
+ wl_data_device_add_listener(wl->dnd_ddev, &data_device_listener, wl);
+ } else {
+ MP_VERBOSE(wl, "Compositor doesn't support the %s (ver. 3) protocol!\n",
+ wl_data_device_manager_interface.name);
}
- // create_display's roundtrip only adds the interfaces
- // the second roundtrip receives output modes, geometry and more ...
- wl_display_roundtrip(wl->display.display);
-
- struct vo_wayland_output *o = NULL;
- wl_list_for_each(o, &wl->display.output_list, link) {
- MP_VERBOSE(wl, "output received:\n"
- "\tvendor: %s\n"
- "\tmodel: %s\n"
- "\tw: %d, h: %d\n"
- "\tscale: %d\n"
- "\tHz: %f\n",
- o->make, o->model,
- o->width, o->height, o->scale,
- o->refresh_rate / 1000.0f);
+ if (wl->server_decoration_manager) {
+ wl->server_decoration = org_kde_kwin_server_decoration_manager_create(wl->server_decoration_manager, wl->surface);
+ set_border_decorations(wl, vo->opts->border);
+ } else {
+ MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
+ org_kde_kwin_server_decoration_manager_interface.name);
}
+ if (!wl->idle_inhibit_manager)
+ MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
+ zwp_idle_inhibit_manager_v1_interface.name);
+
+ wl->display_fd = wl_display_get_fd(wl->display);
mp_make_wakeup_pipe(wl->wakeup_pipe);
return true;
@@ -947,220 +1052,340 @@ int vo_wayland_init(struct vo *vo)
void vo_wayland_uninit(struct vo *vo)
{
- struct vo_wayland_state *wl = vo->wayland;
- destroy_cursor(wl);
- destroy_window(wl);
- destroy_input(wl);
- destroy_display(wl);
+ struct vo_wayland_state *wl = vo->wl;
+ if (!wl)
+ return;
+
+ mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL);
+
+ if (wl->cursor_theme)
+ wl_cursor_theme_destroy(wl->cursor_theme);
+
+ if (wl->cursor_surface)
+ wl_surface_destroy(wl->cursor_surface);
+
+ if (wl->xkb_context)
+ xkb_context_unref(wl->xkb_context);
+
+ if (wl->idle_inhibitor)
+ zwp_idle_inhibitor_v1_destroy(wl->idle_inhibitor);
+
+ if (wl->idle_inhibit_manager)
+ zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager);
+
+ if (wl->shell)
+ zxdg_shell_v6_destroy(wl->shell);
+
+ if (wl->shm)
+ wl_shm_destroy(wl->shm);
+
+ if (wl->dnd_devman)
+ wl_data_device_manager_destroy(wl->dnd_devman);
+
+ if (wl->server_decoration)
+ org_kde_kwin_server_decoration_destroy(wl->server_decoration);
+
+ if (wl->server_decoration_manager)
+ org_kde_kwin_server_decoration_manager_destroy(wl->server_decoration_manager);
+
+ if (wl->surface)
+ wl_surface_destroy(wl->surface);
+
+ if (wl->frame_callback)
+ wl_callback_destroy(wl->frame_callback);
+
+ if (wl->display) {
+ close(wl_display_get_fd(wl->display));
+ wl_display_disconnect(wl->display);
+ }
+
+ struct vo_wayland_output *output, *tmp;
+ wl_list_for_each_safe(output, tmp, &wl->output_list, link)
+ remove_output(output);
+
+ talloc_free(wl->dnd_mime_type);
+
for (int n = 0; n < 2; n++)
close(wl->wakeup_pipe[n]);
talloc_free(wl);
- vo->wayland = NULL;
+ vo->wl = NULL;
}
-static void vo_wayland_ontop(struct vo *vo)
+static struct vo_wayland_output *find_output(struct vo_wayland_state *wl, int index)
{
- struct vo_wayland_state *wl = vo->wayland;
- if (!vo->opts->ontop)
- return;
- MP_DBG(wl, "going ontop\n");
- window_set_toplevel(wl);
- schedule_resize(wl, 0, wl->window.width, wl->window.height);
+ int screen_id = 0;
+ struct vo_wayland_output *output;
+ wl_list_for_each(output, &wl->output_list, link) {
+ if (index == screen_id++)
+ return output;
+ }
+ return NULL;
}
-static void vo_wayland_fullscreen(struct vo *vo)
+int vo_wayland_reconfig(struct vo *vo)
{
- struct vo_wayland_state *wl = vo->wayland;
- if (!wl->display.shell)
- return;
+ struct wl_output *wl_out = NULL;
+ struct mp_rect screenrc = { 0 };
+ struct vo_wayland_state *wl = vo->wl;
+
+ MP_VERBOSE(wl, "Reconfiguring!\n");
+
+ /* Surface enter events happen later but we already know the outputs and we'd
+ * like to know the output the surface would be on (for scaling or fullscreen),
+ * so if fsscreen_id is set or there's only one possible output, use it. */
+ if (((!wl->current_output) && (wl_list_length(&wl->output_list) == 1)) ||
+ (vo->opts->fullscreen && (vo->opts->fsscreen_id >= 0))) {
+ int idx = 0;
+ if (vo->opts->fullscreen && (vo->opts->fsscreen_id >= 0))
+ idx = vo->opts->fsscreen_id;
+ struct vo_wayland_output *out = find_output(wl, idx);
+ if (!out) {
+ MP_ERR(wl, "Screen index %i not found/unavailable!\n", idx);
+ } else {
+ wl_out = out->output;
+ wl->current_output = out;
+ wl->scaling = out->scale;
+ screenrc = wl->current_output->geometry;
+ }
+ }
+
+ struct vo_win_geometry geo;
+ vo_calc_window_geometry(vo, &screenrc, &geo);
+ vo_apply_window_geometry(vo, &geo);
- struct wl_output *fs_output = wl->display.fs_output;
+ wl->geometry.x0 = 0;
+ wl->geometry.y0 = 0;
+ wl->geometry.x1 = vo->dwidth / wl->scaling;
+ wl->geometry.y1 = vo->dheight / wl->scaling;
+ wl->window_size = wl->geometry;
+ wl->aspect_ratio = vo->dwidth / (float)vo->dheight;
if (vo->opts->fullscreen) {
- MP_DBG(wl, "going fullscreen\n");
- wl->window.is_fullscreen = true;
- wl->window.p_width = wl->window.width;
- wl->window.p_height = wl->window.height;
- if (wl->display.current_output)
- schedule_resize(wl, 0, wl->display.current_output->width,
- wl->display.current_output->height);
- wl_shell_surface_set_fullscreen(wl->window.shell_surface,
- WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
- 0, fs_output);
+ /* If already fullscreen, fix resolution for the frame size change */
+ if (wl->fullscreen && wl->current_output) {
+ wl->geometry.x0 = 0;
+ wl->geometry.y0 = 0;
+ wl->geometry.x1 = mp_rect_w(wl->current_output->geometry)/wl->scaling;
+ wl->geometry.y1 = mp_rect_h(wl->current_output->geometry)/wl->scaling;
+ } else {
+ zxdg_toplevel_v6_set_fullscreen(wl->xdg_toplevel, wl_out);
+ }
}
- else {
- MP_DBG(wl, "leaving fullscreen\n");
- wl->window.is_fullscreen = false;
- window_set_toplevel(wl);
- schedule_resize(wl, 0, wl->window.p_width, wl->window.p_height);
+ wl_surface_set_buffer_scale(wl->surface, wl->scaling);
+ wl_surface_commit(wl->surface);
+ wl->pending_vo_events |= VO_EVENT_RESIZE;
+ if (!wl->configured) {
+ if (spawn_cursor(wl))
+ return false;
+ wl_display_roundtrip(wl->display);
+ wl->configured = true;
}
+
+ return true;
}
-static void vo_wayland_update_screeninfo(struct vo *vo, struct mp_rect *screenrc)
+static int set_screensaver_inhibitor(struct vo_wayland_state *wl, int state)
{
- struct vo_wayland_state *wl = vo->wayland;
- struct mp_vo_opts *opts = vo->opts;
-
- *screenrc = (struct mp_rect){0};
+ if (!wl->idle_inhibit_manager)
+ return VO_NOTIMPL;
+ if (state == (!!wl->idle_inhibitor))
+ return VO_TRUE;
+ if (state) {
+ MP_VERBOSE(wl, "Enabling idle inhibitor\n");
+ struct zwp_idle_inhibit_manager_v1 *mgr = wl->idle_inhibit_manager;
+ wl->idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(mgr, wl->surface);
+ } else {
+ MP_VERBOSE(wl, "Disabling the idle inhibitor\n");
+ zwp_idle_inhibitor_v1_destroy(wl->idle_inhibitor);
+ }
+ return VO_TRUE;
+}
- int screen_id = 0;
+static int toggle_fullscreen(struct vo_wayland_state *wl)
+{
+ if (!wl->xdg_toplevel)
+ return VO_NOTAVAIL;
+ if (wl->fullscreen)
+ zxdg_toplevel_v6_unset_fullscreen(wl->xdg_toplevel);
+ else
+ zxdg_toplevel_v6_set_fullscreen(wl->xdg_toplevel, NULL);
+ return VO_TRUE;
+}
- struct vo_wayland_output *output;
- struct vo_wayland_output *first_output = NULL;
- struct vo_wayland_output *fsscreen_output = NULL;
+static int update_window_title(struct vo_wayland_state *wl, char *title)
+{
+ if (!wl->xdg_toplevel)
+ return VO_NOTAVAIL;
+ zxdg_toplevel_v6_set_title(wl->xdg_toplevel, title);
+ return VO_TRUE;
+}
- if (opts->fsscreen_id >= 0) {
- wl_list_for_each_reverse(output, &wl->display.output_list, link) {
- if (!output || !output->width)
- continue;
+static void check_dnd_fd(struct vo_wayland_state *wl)
+{
+ if (wl->dnd_fd == -1)
+ return;
- if (opts->fsscreen_id == screen_id)
- fsscreen_output = output;
+ struct pollfd fdp = { wl->dnd_fd, POLLIN | POLLERR | POLLHUP, 0 };
+ if (poll(&fdp, 1, 0) <= 0)
+ return;
- screen_id++;
+ if (fdp.revents & POLLIN) {
+ ptrdiff_t offset = 0;
+ size_t data_read = 0;
+ const size_t chunk_size = 1;
+ uint8_t *buffer = ta_zalloc_size(wl, chunk_size);
+ if (!buffer)
+ goto end;
+
+ while ((data_read = read(wl->dnd_fd, buffer + offset, chunk_size)) > 0) {
+ offset += data_read;
+ buffer = ta_realloc_size(wl, buffer, offset + chunk_size);
+ memset(buffer + offset, 0, chunk_size);
+ if (!buffer)
+ goto end;
}
- }
- if (fsscreen_output) {
- wl->display.fs_output = fsscreen_output->output;
- screenrc->x1 = fsscreen_output->width;
- screenrc->y1 = fsscreen_output->height;
+ MP_VERBOSE(wl, "Read %td bytes from the DND fd\n", offset);
+
+ struct bstr file_list = bstr0(buffer);
+ mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type,
+ file_list, wl->dnd_action);
+ talloc_free(buffer);
+end:
+ wl_data_offer_finish(wl->dnd_offer);
+ talloc_free(wl->dnd_mime_type);
+ wl->dnd_mime_type = NULL;
+ wl->dnd_mime_score = 0;
}
- else {
- wl->display.fs_output = NULL; /* current output is always 0 */
- if (first_output) {
- screenrc->x1 = wl->display.current_output->width;
- screenrc->y1 = wl->display.current_output->height;
- }
+ if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) {
+ close(wl->dnd_fd);
+ wl->dnd_fd = -1;
}
+}
- wl->window.fs_width = screenrc->x1;
- wl->window.fs_height = screenrc->y1;
+static char **get_displays_spanned(struct vo_wayland_state *wl)
+{
+ char **names = NULL;
+ int displays_spanned = 0;
+ struct vo_wayland_output *output;
+ wl_list_for_each(output, &wl->output_list, link) {
+ if (output->has_surface)
+ MP_TARRAY_APPEND(NULL, names, displays_spanned,
+ talloc_strdup(NULL, output->model));
+ }
+ MP_TARRAY_APPEND(NULL, names, displays_spanned, NULL);
+ return names;
}
int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
{
- struct vo_wayland_state *wl = vo->wayland;
- wl_display_dispatch_pending(wl->display.display);
+ struct vo_wayland_state *wl = vo->wl;
+ wl_display_dispatch_pending(wl->display);
switch (request) {
- case VOCTRL_CHECK_EVENTS:
- *events |= wl->window.events;
- wl->window.events = 0;
+ case VOCTRL_CHECK_EVENTS: {
+ check_dnd_fd(wl);
+ *events |= wl->pending_vo_events;
+ wl->pending_vo_events = 0;
return VO_TRUE;
- case VOCTRL_FULLSCREEN:
- vo_wayland_fullscreen(vo);
+ }
+ case VOCTRL_GET_FULLSCREEN: {
+ *(int *)arg = wl->fullscreen;
return VO_TRUE;
- case VOCTRL_ONTOP:
- vo_wayland_ontop(vo);
+ }
+ case VOCTRL_GET_DISPLAY_NAMES: {
+ *(char ***)arg = get_displays_spanned(wl);
return VO_TRUE;
- case VOCTRL_GET_UNFS_WINDOW_SIZE: {
- int *s = arg, scale = 1;
- if (wl->display.current_output)
- scale = wl->display.current_output->scale;
- s[0] = scale*wl->window.width;
- s[1] = scale*wl->window.height;
+ }
+ case VOCTRL_PAUSE: {
+ wl_callback_destroy(wl->frame_callback);
+ wl->frame_callback = NULL;
+ vo_disable_external_renderloop(wl->vo);
return VO_TRUE;
}
- case VOCTRL_SET_UNFS_WINDOW_SIZE: {
+ case VOCTRL_RESUME: {
+ vo_enable_external_renderloop(wl->vo);
+ frame_callback(wl, NULL, 0);
+ return VO_TRUE;
+ }
+ case VOCTRL_GET_UNFS_WINDOW_SIZE: {
int *s = arg;
- if (!wl->window.is_fullscreen)
- schedule_resize(wl, 0, s[0], s[1]);
+ s[0] = mp_rect_w(wl->geometry)*wl->scaling;
+ s[1] = mp_rect_h(wl->geometry)*wl->scaling;
return VO_TRUE;
}
- case VOCTRL_SET_CURSOR_VISIBILITY:
- if (*(bool *)arg) {
- if (!wl->cursor.visible)
- show_cursor(wl);
- }
- else {
- if (wl->cursor.visible)
- hide_cursor(wl);
+ case VOCTRL_SET_UNFS_WINDOW_SIZE: {
+ int *s = arg;
+ if (!wl->fullscreen) {
+ wl->geometry.x0 = 0;
+ wl->geometry.y0 = 0;
+ wl->geometry.x1 = s[0]/wl->scaling;
+ wl->geometry.y1 = s[1]/wl->scaling;
+ wl->pending_vo_events |= VO_EVENT_RESIZE;
}
- wl->cursor.visible = *(bool *)arg;
- return VO_TRUE;
- case VOCTRL_UPDATE_WINDOW_TITLE:
- window_set_title(wl, (char*) arg);
return VO_TRUE;
+ }
case VOCTRL_GET_DISPLAY_FPS: {
- if (!wl->display.current_output)
- break;
-
- // refresh rate is stored in milli-Hertz (mHz)
- double fps = wl->display.current_output->refresh_rate / 1000.0f;
- *(double*) arg = fps;
+ if (!wl->current_output)
+ return VO_NOTAVAIL;
+ *(double *)arg = wl->current_output->refresh_rate;
return VO_TRUE;
}
+ case VOCTRL_UPDATE_WINDOW_TITLE:
+ return update_window_title(wl, (char *)arg);
+ case VOCTRL_FULLSCREEN:
+ return toggle_fullscreen(wl);
+ case VOCTRL_SET_CURSOR_VISIBILITY:
+ return set_cursor_visibility(wl, *(bool *)arg);
+ case VOCTRL_BORDER:
+ return set_border_decorations(wl, vo->opts->border);
+ case VOCTRL_KILL_SCREENSAVER:
+ return set_screensaver_inhibitor(wl, true);
+ case VOCTRL_RESTORE_SCREENSAVER:
+ return set_screensaver_inhibitor(wl, false);
}
- return VO_NOTIMPL;
-}
-bool vo_wayland_config(struct vo *vo)
-{
- struct vo_wayland_state *wl = vo->wayland;
-
- struct mp_rect screenrc;
- vo_wayland_update_screeninfo(vo, &screenrc);
-
- struct vo_win_geometry geo;
- vo_calc_window_geometry(vo, &screenrc, &geo);
- vo_apply_window_geometry(vo, &geo);
-
- wl->window.p_width = vo->dwidth;
- wl->window.p_height = vo->dheight;
- wl->window.aspect = vo->dwidth / (float) MPMAX(vo->dheight, 1);
-
- wl->window.width = vo->dwidth;
- wl->window.height = vo->dheight;
- vo_wayland_fullscreen(vo);
-
- return true;
-}
-
-void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb)
-{
- struct vo_wayland_state *wl = vo->wayland;
- wl->frame.data = data;
- wl->frame.function = cb;
- MP_DBG(wl, "restart frame callback\n");
- frame_callback(wl, NULL, 0);
+ return VO_NOTIMPL;
}
void vo_wayland_wakeup(struct vo *vo)
{
- struct vo_wayland_state *wl = vo->wayland;
+ struct vo_wayland_state *wl = vo->wl;
(void)write(wl->wakeup_pipe[1], &(char){0}, 1);
}
void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us)
{
- struct vo_wayland_state *wl = vo->wayland;
- struct wl_display *dp = wl->display.display;
+ struct vo_wayland_state *wl = vo->wl;
+ struct wl_display *display = wl->display;
+
+ if (wl->display_fd == -1)
+ return;
struct pollfd fds[2] = {
- {.fd = wl->display.display_fd, .events = POLLIN },
- {.fd = wl->wakeup_pipe[0], .events = POLLIN },
+ {.fd = wl->display_fd, .events = POLLIN },
+ {.fd = wl->wakeup_pipe[0], .events = POLLIN },
};
int64_t wait_us = until_time_us - mp_time_us();
int timeout_ms = MPCLAMP((wait_us + 999) / 1000, 0, 10000);
- wl_display_dispatch_pending(dp);
- wl_display_flush(dp);
+ wl_display_dispatch_pending(display);
+ wl_display_flush(display);
poll(fds, 2, timeout_ms);
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
- MP_FATAL(wl, "error occurred on the display fd: "
- "closing file descriptor\n");
- close(wl->display.display_fd);
+ MP_FATAL(wl, "Error occurred on the display fd, closing\n");
+ close(wl->display_fd);
+ wl->display_fd = -1;
mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN);
}
if (fds[0].revents & POLLIN)
- wl_display_dispatch(dp);
+ wl_display_dispatch(display);
if (fds[1].revents & POLLIN)
mp_flush_wakeup_pipe(wl->wakeup_pipe[0]);
diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h
index 4bb90d6..4911009 100644
--- a/video/out/wayland_common.h
+++ b/video/out/wayland_common.h
@@ -1,6 +1,5 @@
/*
* This file is part of mpv video player.
- * Copyright © 2013 Alexander Preisinger <alexander.preisinger@gmail.com>
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,133 +18,96 @@
#ifndef MPLAYER_WAYLAND_COMMON_H
#define MPLAYER_WAYLAND_COMMON_H
-#include <stdint.h>
-#include <stdbool.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
-#include "config.h"
-
-#if HAVE_GL_WAYLAND
-#include <wayland-egl.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#endif
-
-struct vo;
+#include "vo.h"
+#include "input/event.h"
struct vo_wayland_output {
- uint32_t id; /* unique name */
+ struct vo_wayland_state *wl;
+ uint32_t id;
struct wl_output *output;
+ struct mp_rect geometry;
+ int phys_width;
+ int phys_height;
+ int scale;
uint32_t flags;
- int32_t width;
- int32_t height;
- int32_t scale;
- int32_t refresh_rate; // fps (mHz)
- const char *make;
- const char *model;
+ double refresh_rate;
+ char *make;
+ char *model;
+ bool has_surface;
struct wl_list link;
};
-typedef void (*vo_wayland_frame_cb)(void *data, uint32_t time);
-
struct vo_wayland_state {
- struct vo *vo;
- struct mp_log* log;
+ struct mp_log *log;
+ struct vo *vo;
+ struct wl_display *display;
+ struct wl_shm *shm;
+ struct wl_compositor *compositor;
+ struct wl_registry *registry;
+
+ /* State */
+ struct mp_rect geometry;
+ struct mp_rect window_size;
+ float aspect_ratio;
+ bool fullscreen;
+ bool configured;
int wakeup_pipe[2];
-
- struct {
- void *data;
- vo_wayland_frame_cb function;
- struct wl_callback *callback;
- } frame;
-
-#if HAVE_GL_WAYLAND
- struct {
- EGLSurface egl_surface;
-
- struct wl_egl_window *egl_window;
-
- struct {
- EGLDisplay dpy;
- EGLContext ctx;
- EGLConfig conf;
- } egl;
- } egl_context;
-#endif
-
- struct {
- int fd;
- struct wl_display *display;
- struct wl_registry *registry;
- struct wl_compositor *compositor;
- struct wl_shell *shell;
-
- struct wl_list output_list;
- struct wl_output *fs_output; /* fullscreen output */
- struct vo_wayland_output *current_output;
-
- int display_fd;
-
- struct wl_shm *shm;
-
- struct wl_subcompositor *subcomp;
- } display;
-
- struct {
- int32_t width; // current size of the window
- int32_t height;
- int32_t p_width; // previous sizes for leaving fullscreen
- int32_t p_height;
- int32_t sh_width; // sheduled width for resizing
- int32_t sh_height;
- int32_t sh_x; // x, y calculated with the drag edges for moving
- int32_t sh_y;
- float aspect;
-
- bool is_fullscreen; // don't keep aspect ratio in fullscreen mode
- int32_t fs_width; // fullscreen sizes
- int32_t fs_height;
-
- struct wl_surface *video_surface;
- int32_t mouse_x; // mouse position inside the surface
- int32_t mouse_y;
- struct wl_shell_surface *shell_surface;
- int events; /* mplayer events (VO_EVENT_RESIZE) */
- } window;
-
- struct {
- struct wl_cursor *default_cursor;
- struct wl_cursor_theme *theme;
- struct wl_surface *surface;
-
- /* pointer for fading out */
- bool visible;
- struct wl_pointer *pointer;
- uint32_t serial;
- } cursor;
-
- struct {
- struct wl_seat *seat;
- struct wl_keyboard *keyboard;
- struct wl_pointer *pointer;
-
- struct {
- struct xkb_context *context;
- struct xkb_keymap *keymap;
- struct xkb_state *state;
- } xkb;
- } input;
+ int pending_vo_events;
+ int mouse_x;
+ int mouse_y;
+ int scaling;
+ int touch_entries;
+ uint32_t pointer_id;
+ int display_fd;
+ struct wl_callback *frame_callback;
+ struct wl_list output_list;
+ struct vo_wayland_output *current_output;
+
+ /* Shell */
+ struct wl_surface *surface;
+ struct zxdg_shell_v6 *shell;
+ struct zxdg_toplevel_v6 *xdg_toplevel;
+ struct zxdg_surface_v6 *xdg_surface;
+ struct org_kde_kwin_server_decoration_manager *server_decoration_manager;
+ struct org_kde_kwin_server_decoration *server_decoration;
+ struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;
+ struct zwp_idle_inhibitor_v1 *idle_inhibitor;
+
+ /* Input */
+ struct wl_seat *seat;
+ struct wl_pointer *pointer;
+ struct wl_touch *touch;
+ struct wl_keyboard *keyboard;
+ struct xkb_context *xkb_context;
+ struct xkb_keymap *xkb_keymap;
+ struct xkb_state *xkb_state;
+
+ /* DND */
+ struct wl_data_device_manager *dnd_devman;
+ struct wl_data_device *dnd_ddev;
+ struct wl_data_offer *dnd_offer;
+ enum mp_dnd_action dnd_action;
+ char *dnd_mime_type;
+ int dnd_mime_score;
+ int dnd_fd;
+
+ /* Cursor */
+ struct wl_cursor_theme *cursor_theme;
+ struct wl_cursor *default_cursor;
+ struct wl_surface *cursor_surface;
+ int allocated_cursor_scale;
};
int vo_wayland_init(struct vo *vo);
-void vo_wayland_uninit(struct vo *vo);
-bool vo_wayland_config(struct vo *vo);
+int vo_wayland_reconfig(struct vo *vo);
int vo_wayland_control(struct vo *vo, int *events, int request, void *arg);
+void vo_wayland_check_events(struct vo *vo);
+void vo_wayland_uninit(struct vo *vo);
void vo_wayland_wakeup(struct vo *vo);
void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us);
-void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb);
#endif /* MPLAYER_WAYLAND_COMMON_H */
-
diff --git a/video/out/win32/exclusive_hack.c b/video/out/win32/exclusive_hack.c
deleted file mode 100644
index 668dfd5..0000000
--- a/video/out/win32/exclusive_hack.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv 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.
- *
- * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <windows.h>
-#include <winternl.h>
-#include <pthread.h>
-
-#include "exclusive_hack.h"
-
-// Missing NT API definitions
-typedef enum _MP_MUTANT_INFORMATION_CLASS {
- MpMutantBasicInformation
-} MP_MUTANT_INFORMATION_CLASS;
-#define MUTANT_INFORMATION_CLASS MP_MUTANT_INFORMATION_CLASS
-#define MutantBasicInformation MpMutantBasicInformation
-
-typedef struct _MP_MUTANT_BASIC_INFORMATION {
- LONG CurrentCount;
- BOOLEAN OwnedByCaller;
- BOOLEAN AbandonedState;
-} MP_MUTANT_BASIC_INFORMATION;
-#define MUTANT_BASIC_INFORMATION MP_MUTANT_BASIC_INFORMATION
-
-static pthread_once_t internal_api_load_ran = PTHREAD_ONCE_INIT;
-static bool internal_api_loaded = false;
-
-static HANDLE excl_mode_mutex;
-static NTSTATUS (NTAPI *pNtQueryMutant)(HANDLE MutantHandle,
- MUTANT_INFORMATION_CLASS MutantInformationClass, PVOID MutantInformation,
- ULONG MutantInformationLength, PULONG ReturnLength);
-
-static void internal_api_load(void)
-{
- HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
- if (!ntdll)
- return;
- pNtQueryMutant = (void*)GetProcAddress(ntdll, "NtQueryMutant");
- if (!pNtQueryMutant)
- return;
- excl_mode_mutex = OpenMutexW(MUTANT_QUERY_STATE, FALSE,
- L"Local\\__DDrawExclMode__");
- if (!excl_mode_mutex)
- return;
-
- internal_api_loaded = true;
-}
-
-bool mp_w32_is_in_exclusive_mode(void)
-{
- pthread_once(&internal_api_load_ran, internal_api_load);
- if (!internal_api_loaded)
- return false;
-
- // As far as we can tell, there is no way to know if a specific OpenGL
- // program is being redirected by the DWM. It is possible, however, to
- // know if some program on the computer is unredirected by the DWM, that
- // is, whether some program is in exclusive fullscreen mode. Exclusive
- // fullscreen programs acquire an undocumented mutex: __DDrawExclMode__. If
- // this is acquired, it's probably by mpv. Even if it isn't, the penalty
- // for incorrectly guessing true (dropped frames) is better than the
- // penalty for incorrectly guessing false (tearing.)
-
- // Testing this mutex is another problem. There is no public function for
- // testing a mutex without attempting to acquire it, but that method won't
- // work because if mpv is in fullscreen, the mutex will already be acquired
- // by this thread (in ddraw.dll) and Windows will happily let it be
- // acquired again. Instead, use the undocumented NtQueryMutant function to
- // test the mutex.
-
- // Note: SHQueryUserNotificationState uses this mutex internally, but it is
- // probably not suitable because it sends a message to the shell instead of
- // testing the mutex directly. mpv will check for exclusive mode once per
- // frame, so if the shell is not running or not responding, it may cause
- // performance issues.
-
- MUTANT_BASIC_INFORMATION mbi;
- NTSTATUS s = pNtQueryMutant(excl_mode_mutex, MutantBasicInformation, &mbi,
- sizeof mbi, NULL);
- if (!NT_SUCCESS(s))
- return false;
-
- return !mbi.CurrentCount;
-}
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index e69640c..1c00963 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -29,6 +29,11 @@
#include "common/common.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
struct vo;
struct mp_log;
diff --git a/video/sws_utils.c b/video/sws_utils.c
index b1ab499..ae6df02 100644
--- a/video/sws_utils.c
+++ b/video/sws_utils.c
@@ -1,20 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- *
- * Almost LGPL.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
diff --git a/video/vaapi.c b/video/vaapi.c
index 99a6272..152b52f 100644
--- a/video/vaapi.c
+++ b/video/vaapi.c
@@ -1,18 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
@@ -30,15 +30,6 @@
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_vaapi.h>
-bool check_va_status(struct mp_log *log, VAStatus status, const char *msg)
-{
- if (status != VA_STATUS_SUCCESS) {
- mp_err(log, "%s: %s\n", msg, vaErrorStr(status));
- return false;
- }
- return true;
-}
-
int va_get_colorspace_flag(enum mp_csp csp)
{
switch (csp) {
@@ -49,72 +40,27 @@ int va_get_colorspace_flag(enum mp_csp csp)
return 0;
}
-struct fmtentry {
- uint32_t va;
- enum mp_imgfmt mp;
-};
-
-static const struct fmtentry va_to_imgfmt[] = {
- {VA_FOURCC_NV12, IMGFMT_NV12},
- {VA_FOURCC_YV12, IMGFMT_420P},
- {VA_FOURCC_IYUV, IMGFMT_420P},
- {VA_FOURCC_UYVY, IMGFMT_UYVY},
- // Note: not sure about endian issues (the mp formats are byte-addressed)
- {VA_FOURCC_RGBA, IMGFMT_RGBA},
- {VA_FOURCC_RGBX, IMGFMT_RGBA},
- {VA_FOURCC_BGRA, IMGFMT_BGRA},
- {VA_FOURCC_BGRX, IMGFMT_BGRA},
- {0 , IMGFMT_NONE}
-};
-
-enum mp_imgfmt va_fourcc_to_imgfmt(uint32_t fourcc)
+#if VA_CHECK_VERSION(1, 0, 0)
+static void va_message_callback(void *context, const char *msg, int mp_level)
{
- for (const struct fmtentry *entry = va_to_imgfmt; entry->va; ++entry) {
- if (entry->va == fourcc)
- return entry->mp;
- }
- return IMGFMT_NONE;
+ struct mp_vaapi_ctx *res = context;
+ mp_msg(res->log, mp_level, "libva: %s", msg);
}
-uint32_t va_fourcc_from_imgfmt(int imgfmt)
+static void va_error_callback(void *context, const char *msg)
{
- for (const struct fmtentry *entry = va_to_imgfmt; entry->va; ++entry) {
- if (entry->mp == imgfmt)
- return entry->va;
- }
- return 0;
+ va_message_callback(context, msg, MSGL_ERR);
}
-static struct mp_image *ctx_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool)
-{
- return va_surface_download(mpi, swpool);
-}
-
-struct va_image_formats {
- VAImageFormat *entries;
- int num;
-};
-
-static void va_get_formats(struct mp_vaapi_ctx *ctx)
+static void va_info_callback(void *context, const char *msg)
{
- struct va_image_formats *formats = talloc_ptrtype(ctx, formats);
- formats->num = vaMaxNumImageFormats(ctx->display);
- formats->entries = talloc_array(formats, VAImageFormat, formats->num);
- VAStatus status = vaQueryImageFormats(ctx->display, formats->entries,
- &formats->num);
- if (!CHECK_VA_STATUS(ctx, "vaQueryImageFormats()"))
- return;
- MP_VERBOSE(ctx, "%d image formats available:\n", formats->num);
- for (int i = 0; i < formats->num; i++)
- MP_VERBOSE(ctx, " %s\n", mp_tag_str(formats->entries[i].fourcc));
- ctx->image_formats = formats;
+ va_message_callback(context, msg, MSGL_V);
}
-
-// VA message callbacks are global and do not have a context parameter, so it's
-// impossible to know from which VADisplay they originate. Try to route them
-// to existing mpv/libmpv instances within this process.
+#else
+// Pre-libva2 VA message callbacks are global and do not have a context
+// parameter, so it's impossible to know from which VADisplay they
+// originate. Try to route them to existing mpv/libmpv instances within
+// this process.
static pthread_mutex_t va_log_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct mp_vaapi_ctx **va_mpv_clients;
static int num_va_mpv_clients;
@@ -149,38 +95,61 @@ static void va_info_callback(const char *msg)
{
va_message_callback(msg, MSGL_V);
}
+#endif
-static void open_lavu_vaapi_device(struct mp_vaapi_ctx *ctx)
+static void free_device_ref(struct AVHWDeviceContext *hwctx)
{
- ctx->av_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
- if (!ctx->av_device_ref)
- return;
+ struct mp_vaapi_ctx *ctx = hwctx->user_opaque;
- AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data;
- AVVAAPIDeviceContext *vactx = hwctx->hwctx;
+ if (ctx->display)
+ vaTerminate(ctx->display);
- vactx->display = ctx->display;
+ if (ctx->destroy_native_ctx)
+ ctx->destroy_native_ctx(ctx->native_ctx);
- if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0)
- av_buffer_unref(&ctx->av_device_ref);
+#if !VA_CHECK_VERSION(1, 0, 0)
+ pthread_mutex_lock(&va_log_mutex);
+ for (int n = 0; n < num_va_mpv_clients; n++) {
+ if (va_mpv_clients[n] == ctx) {
+ MP_TARRAY_REMOVE_AT(va_mpv_clients, num_va_mpv_clients, n);
+ break;
+ }
+ }
+ if (num_va_mpv_clients == 0)
+ TA_FREEP(&va_mpv_clients); // avoid triggering leak detectors
+ pthread_mutex_unlock(&va_log_mutex);
+#endif
- ctx->hwctx.av_device_ref = ctx->av_device_ref;
+ talloc_free(ctx);
}
struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
bool probing)
{
+ AVBufferRef *avref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
+ if (!avref)
+ return NULL;
+
+ AVHWDeviceContext *hwctx = (void *)avref->data;
+ AVVAAPIDeviceContext *vactx = hwctx->hwctx;
+
struct mp_vaapi_ctx *res = talloc_ptrtype(NULL, res);
*res = (struct mp_vaapi_ctx) {
.log = mp_log_new(res, plog, "/vaapi"),
.display = display,
+ .av_device_ref = avref,
.hwctx = {
- .type = HWDEC_VAAPI,
- .ctx = res,
- .download_image = ctx_download_image,
+ .av_device_ref = avref,
},
};
+ hwctx->free = free_device_ref;
+ hwctx->user_opaque = res;
+
+#if VA_CHECK_VERSION(1, 0, 0)
+ vaSetErrorCallback(display, va_error_callback, res);
+ vaSetInfoCallback(display, va_info_callback, res);
+#else
pthread_mutex_lock(&va_log_mutex);
MP_TARRAY_APPEND(NULL, va_mpv_clients, num_va_mpv_clients, res);
pthread_mutex_unlock(&va_log_mutex);
@@ -191,26 +160,22 @@ struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
vaSetErrorCallback(va_error_callback);
vaSetInfoCallback(va_info_callback);
#endif
+#endif
- int major_version, minor_version;
- int status = vaInitialize(display, &major_version, &minor_version);
- if (status != VA_STATUS_SUCCESS && probing)
- goto error;
- if (!check_va_status(res->log, status, "vaInitialize()"))
+ int major, minor;
+ int status = vaInitialize(display, &major, &minor);
+ if (status != VA_STATUS_SUCCESS) {
+ if (!probing)
+ MP_ERR(res, "Failed to initialize VAAPI: %s\n", vaErrorStr(status));
goto error;
+ }
+ MP_VERBOSE(res, "Initialized VAAPI: version %d.%d\n", major, minor);
- MP_VERBOSE(res, "VA API version %d.%d\n", major_version, minor_version);
+ vactx->display = res->display;
- va_get_formats(res);
- if (!res->image_formats)
+ if (av_hwdevice_ctx_init(res->av_device_ref) < 0)
goto error;
- // For now, some code will still work even if libavutil fails on old crap
- // libva drivers (such as the vdpau wraper). So don't error out on failure.
- open_lavu_vaapi_device(res);
-
- res->hwctx.emulated = va_guess_if_emulated(res);
-
return res;
error:
@@ -222,410 +187,32 @@ error:
// Undo va_initialize, and close the VADisplay.
void va_destroy(struct mp_vaapi_ctx *ctx)
{
- if (ctx) {
- av_buffer_unref(&ctx->av_device_ref);
-
- if (ctx->display)
- vaTerminate(ctx->display);
-
- if (ctx->destroy_native_ctx)
- ctx->destroy_native_ctx(ctx->native_ctx);
-
- pthread_mutex_lock(&va_log_mutex);
- for (int n = 0; n < num_va_mpv_clients; n++) {
- if (va_mpv_clients[n] == ctx) {
- MP_TARRAY_REMOVE_AT(va_mpv_clients, num_va_mpv_clients, n);
- break;
- }
- }
- if (num_va_mpv_clients == 0)
- TA_FREEP(&va_mpv_clients); // avoid triggering leak detectors
- pthread_mutex_unlock(&va_log_mutex);
-
- talloc_free(ctx);
- }
-}
+ if (!ctx)
+ return;
-VAImageFormat *va_image_format_from_imgfmt(struct mp_vaapi_ctx *ctx, int imgfmt)
-{
- struct va_image_formats *formats = ctx->image_formats;
- const int fourcc = va_fourcc_from_imgfmt(imgfmt);
- if (!formats || !formats->num || !fourcc)
- return NULL;
- for (int i = 0; i < formats->num; i++) {
- if (formats->entries[i].fourcc == fourcc)
- return &formats->entries[i];
- }
- return NULL;
+ AVBufferRef *ref = ctx->av_device_ref;
+ av_buffer_unref(&ref); // frees ctx as well
}
-struct va_surface {
- struct mp_vaapi_ctx *ctx;
- VADisplay display;
-
- VASurfaceID id;
- int rt_format;
-
- // The actually allocated surface size (needed for cropping).
- // mp_images can have a smaller size than this, which means they are
- // cropped down to a smaller size by removing right/bottom pixels.
- int w, h;
-
- VAImage image; // used for software decoding case
- bool is_derived; // is image derived by vaDeriveImage()?
-};
-
VASurfaceID va_surface_id(struct mp_image *mpi)
{
return mpi && mpi->imgfmt == IMGFMT_VAAPI ?
(VASurfaceID)(uintptr_t)mpi->planes[3] : VA_INVALID_ID;
}
-static struct va_surface *va_surface_in_mp_image(struct mp_image *mpi)
-{
- return mpi && mpi->imgfmt == IMGFMT_VAAPI ?
- (struct va_surface*)mpi->planes[0] : NULL;
-}
-
-int va_surface_rt_format(struct mp_image *mpi)
-{
- struct va_surface *surface = va_surface_in_mp_image(mpi);
- return surface ? surface->rt_format : 0;
-}
-
-// Return the real size of the underlying surface. (HW decoding might allocate
-// padded surfaces for example.)
-void va_surface_get_uncropped_size(struct mp_image *mpi, int *out_w, int *out_h)
-{
- if (mpi->hwctx) {
- AVHWFramesContext *fctx = (void *)mpi->hwctx->data;
- *out_w = fctx->width;
- *out_h = fctx->height;
- } else {
- struct va_surface *s = va_surface_in_mp_image(mpi);
- *out_w = s ? s->w : 0;
- *out_h = s ? s->h : 0;
- }
-}
-
-static void release_va_surface(void *arg)
-{
- struct va_surface *surface = arg;
-
- if (surface->id != VA_INVALID_ID) {
- if (surface->image.image_id != VA_INVALID_ID)
- vaDestroyImage(surface->display, surface->image.image_id);
- vaDestroySurfaces(surface->display, &surface->id, 1);
- }
-
- talloc_free(surface);
-}
-
-static struct mp_image *alloc_surface(struct mp_vaapi_ctx *ctx, int rt_format,
- int w, int h)
-{
- VASurfaceID id = VA_INVALID_ID;
- VAStatus status;
- status = vaCreateSurfaces(ctx->display, rt_format, w, h, &id, 1, NULL, 0);
- if (!CHECK_VA_STATUS(ctx, "vaCreateSurfaces()"))
- return NULL;
-
- struct va_surface *surface = talloc_ptrtype(NULL, surface);
- if (!surface)
- return NULL;
-
- *surface = (struct va_surface){
- .ctx = ctx,
- .id = id,
- .rt_format = rt_format,
- .w = w,
- .h = h,
- .display = ctx->display,
- .image = { .image_id = VA_INVALID_ID, .buf = VA_INVALID_ID },
- };
-
- struct mp_image img = {0};
- mp_image_setfmt(&img, IMGFMT_VAAPI);
- mp_image_set_size(&img, w, h);
- img.planes[0] = (uint8_t*)surface;
- img.planes[3] = (uint8_t*)(uintptr_t)surface->id;
- return mp_image_new_custom_ref(&img, surface, release_va_surface);
-}
-
-static void va_surface_image_destroy(struct va_surface *surface)
-{
- if (!surface || surface->image.image_id == VA_INVALID_ID)
- return;
- vaDestroyImage(surface->display, surface->image.image_id);
- surface->image.image_id = VA_INVALID_ID;
- surface->is_derived = false;
-}
-
-static int va_surface_image_alloc(struct va_surface *p, VAImageFormat *format)
-{
- VADisplay *display = p->display;
-
- if (p->image.image_id != VA_INVALID_ID &&
- p->image.format.fourcc == format->fourcc)
- return 0;
-
- int r = 0;
-
- va_surface_image_destroy(p);
-
- VAStatus status = vaDeriveImage(display, p->id, &p->image);
- if (status == VA_STATUS_SUCCESS) {
- /* vaDeriveImage() is supported, check format */
- if (p->image.format.fourcc == format->fourcc &&
- p->image.width == p->w && p->image.height == p->h)
- {
- p->is_derived = true;
- MP_TRACE(p->ctx, "Using vaDeriveImage()\n");
- } else {
- vaDestroyImage(p->display, p->image.image_id);
- status = VA_STATUS_ERROR_OPERATION_FAILED;
- }
- }
- if (status != VA_STATUS_SUCCESS) {
- p->image.image_id = VA_INVALID_ID;
- status = vaCreateImage(p->display, format, p->w, p->h, &p->image);
- if (!CHECK_VA_STATUS(p->ctx, "vaCreateImage()")) {
- p->image.image_id = VA_INVALID_ID;
- r = -1;
- }
- }
-
- return r;
-}
-
-// img must be a VAAPI surface; make sure its internal VAImage is allocated
-// to a format corresponding to imgfmt (or return an error).
-int va_surface_alloc_imgfmt(struct mp_image *img, int imgfmt)
-{
- struct va_surface *p = va_surface_in_mp_image(img);
- if (!p)
- return -1;
- // Multiple FourCCs can refer to the same imgfmt, so check by doing the
- // surjective conversion first.
- if (p->image.image_id != VA_INVALID_ID &&
- va_fourcc_to_imgfmt(p->image.format.fourcc) == imgfmt)
- return 0;
- VAImageFormat *format = va_image_format_from_imgfmt(p->ctx, imgfmt);
- if (!format)
- return -1;
- if (va_surface_image_alloc(p, format) < 0)
- return -1;
- return 0;
-}
-
-bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image, struct mp_image *mpi)
+static bool is_emulated(struct AVBufferRef *hw_device_ctx)
{
- int imgfmt = va_fourcc_to_imgfmt(image->format.fourcc);
- if (imgfmt == IMGFMT_NONE)
- return false;
- void *data = NULL;
- const VAStatus status = vaMapBuffer(ctx->display, image->buf, &data);
- if (!CHECK_VA_STATUS(ctx, "vaMapBuffer()"))
- return false;
-
- *mpi = (struct mp_image) {0};
- mp_image_setfmt(mpi, imgfmt);
- mp_image_set_size(mpi, image->width, image->height);
-
- for (int p = 0; p < image->num_planes; p++) {
- mpi->stride[p] = image->pitches[p];
- mpi->planes[p] = (uint8_t *)data + image->offsets[p];
- }
-
- if (image->format.fourcc == VA_FOURCC_YV12) {
- MPSWAP(int, mpi->stride[1], mpi->stride[2]);
- MPSWAP(uint8_t *, mpi->planes[1], mpi->planes[2]);
- }
-
- return true;
-}
-
-bool va_image_unmap(struct mp_vaapi_ctx *ctx, VAImage *image)
-{
- const VAStatus status = vaUnmapBuffer(ctx->display, image->buf);
- return CHECK_VA_STATUS(ctx, "vaUnmapBuffer()");
-}
-
-// va_dst: copy destination, must be IMGFMT_VAAPI
-// sw_src: copy source, must be a software pixel format
-int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src)
-{
- struct va_surface *p = va_surface_in_mp_image(va_dst);
- if (!p)
- return -1;
-
- if (va_surface_alloc_imgfmt(va_dst, sw_src->imgfmt) < 0)
- return -1;
-
- struct mp_image img;
- if (!va_image_map(p->ctx, &p->image, &img))
- return -1;
- assert(sw_src->w <= img.w && sw_src->h <= img.h);
- mp_image_set_size(&img, sw_src->w, sw_src->h); // copy only visible part
- mp_image_copy(&img, sw_src);
- va_image_unmap(p->ctx, &p->image);
-
- if (!p->is_derived) {
- VAStatus status = vaPutImage(p->display, p->id,
- p->image.image_id,
- 0, 0, sw_src->w, sw_src->h,
- 0, 0, sw_src->w, sw_src->h);
- if (!CHECK_VA_STATUS(p->ctx, "vaPutImage()"))
- return -1;
- }
-
- if (p->is_derived)
- va_surface_image_destroy(p);
- return 0;
-}
-
-static struct mp_image *try_download(struct va_surface *p, struct mp_image *src,
- struct mp_image_pool *pool)
-{
- VAStatus status;
- VAImage *image = &p->image;
-
- if (image->image_id == VA_INVALID_ID ||
- !va_fourcc_to_imgfmt(image->format.fourcc))
- return NULL;
-
- if (!p->is_derived) {
- status = vaGetImage(p->display, p->id, 0, 0,
- p->w, p->h, image->image_id);
- if (status != VA_STATUS_SUCCESS)
- return NULL;
- }
-
- struct mp_image *dst = NULL;
- struct mp_image tmp;
- if (va_image_map(p->ctx, image, &tmp)) {
- assert(src->w <= tmp.w && src->h <= tmp.h);
- mp_image_set_size(&tmp, src->w, src->h); // copy only visible part
- dst = mp_image_pool_get(pool, tmp.imgfmt, tmp.w, tmp.h);
- if (dst) {
- mp_check_gpu_memcpy(p->ctx->log, &p->ctx->gpu_memcpy_message);
-
- mp_image_copy_gpu(dst, &tmp);
- mp_image_copy_attributes(dst, src);
- }
- va_image_unmap(p->ctx, image);
- }
- if (p->is_derived)
- va_surface_image_destroy(p);
- return dst;
-}
-
-// Return a software copy of the IMGFMT_VAAPI src image.
-// pool is optional (used for allocating returned images).
-struct mp_image *va_surface_download(struct mp_image *src,
- struct mp_image_pool *pool)
-{
- if (!src || src->imgfmt != IMGFMT_VAAPI)
- return NULL;
- struct va_surface *p = va_surface_in_mp_image(src);
- if (!p) {
- // We might still be able to get to the cheese if this is a surface
- // produced by libavutil's vaapi glue code.
- return mp_image_hw_download(src, pool);
- }
- struct mp_image *mpi = NULL;
- struct mp_vaapi_ctx *ctx = p->ctx;
- VAStatus status = vaSyncSurface(p->display, p->id);
- if (!CHECK_VA_STATUS(ctx, "vaSyncSurface()"))
- goto done;
-
- mpi = try_download(p, src, pool);
- if (mpi)
- goto done;
-
- // We have no clue which format will work, so try them all.
- // Make sure to start with the most preferred format (nv12), to avoid
- // slower code paths.
- for (int n = 0; va_to_imgfmt[n].mp; n++) {
- VAImageFormat *format =
- va_image_format_from_imgfmt(ctx, va_to_imgfmt[n].mp);
- if (format) {
- if (va_surface_image_alloc(p, format) < 0)
- continue;
- mpi = try_download(p, src, pool);
- if (mpi)
- goto done;
- }
- }
-
-done:
-
- if (!mpi)
- MP_ERR(ctx, "failed to get surface data.\n");
- return mpi;
-}
-
-// Set the hw_subfmt from the surface's real format. Because of this bug:
-// https://bugs.freedesktop.org/show_bug.cgi?id=79848
-// it should be assumed that the real format is only known after an arbitrary
-// vaCreateContext() call has been made, or even better, after the surface
-// has been rendered to.
-// If the hw_subfmt is already set, this is a NOP.
-void va_surface_init_subformat(struct mp_image *mpi)
-{
- VAStatus status;
- if (mpi->params.hw_subfmt)
- return;
- struct va_surface *p = va_surface_in_mp_image(mpi);
- if (!p)
- return;
-
- VAImage va_image = { .image_id = VA_INVALID_ID };
-
- status = vaDeriveImage(p->display, va_surface_id(mpi), &va_image);
- if (status != VA_STATUS_SUCCESS)
- goto err;
-
- mpi->params.hw_subfmt = va_fourcc_to_imgfmt(va_image.format.fourcc);
-
- status = vaDestroyImage(p->display, va_image.image_id);
- CHECK_VA_STATUS(p->ctx, "vaDestroyImage()");
-
-err: ;
-}
-
-struct pool_alloc_ctx {
- struct mp_vaapi_ctx *vaapi;
- int rt_format;
-};
-
-static struct mp_image *alloc_pool(void *pctx, int fmt, int w, int h)
-{
- struct pool_alloc_ctx *alloc_ctx = pctx;
- if (fmt != IMGFMT_VAAPI)
- return NULL;
+ AVHWDeviceContext *hwctx = (void *)hw_device_ctx->data;
+ AVVAAPIDeviceContext *vactx = hwctx->hwctx;
- return alloc_surface(alloc_ctx->vaapi, alloc_ctx->rt_format, w, h);
+ const char *s = vaQueryVendorString(vactx->display);
+ return s && strstr(s, "VDPAU backend");
}
-// The allocator of the given image pool to allocate VAAPI surfaces, using
-// the given rt_format.
-void va_pool_set_allocator(struct mp_image_pool *pool, struct mp_vaapi_ctx *ctx,
- int rt_format)
-{
- struct pool_alloc_ctx *alloc_ctx = talloc_ptrtype(pool, alloc_ctx);
- *alloc_ctx = (struct pool_alloc_ctx){
- .vaapi = ctx,
- .rt_format = rt_format,
- };
- mp_image_pool_set_allocator(pool, alloc_pool, alloc_ctx);
- mp_image_pool_set_lru(pool);
-}
bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx)
{
- const char *s = vaQueryVendorString(ctx->display);
- return s && strstr(s, "VDPAU backend");
+ return is_emulated(ctx->av_device_ref);
}
struct va_native_display {
@@ -719,20 +306,16 @@ static const struct va_native_display *const native_displays[] = {
NULL
};
-static void va_destroy_ctx(struct mp_hwdec_ctx *ctx)
-{
- va_destroy(ctx->ctx);
-}
-
-struct mp_hwdec_ctx *va_create_standalone(struct mpv_global *global,
- struct mp_log *plog, bool probing)
+static struct AVBufferRef *va_create_standalone(struct mpv_global *global,
+ struct mp_log *log, struct hwcontext_create_dev_params *params)
{
for (int n = 0; native_displays[n]; n++) {
VADisplay *display = NULL;
void *native_ctx = NULL;
native_displays[n]->create(&display, &native_ctx);
if (display) {
- struct mp_vaapi_ctx *ctx = va_initialize(display, plog, probing);
+ struct mp_vaapi_ctx *ctx =
+ va_initialize(display, log, params->probing);
if (!ctx) {
vaTerminate(display);
native_displays[n]->destroy(native_ctx);
@@ -740,9 +323,14 @@ struct mp_hwdec_ctx *va_create_standalone(struct mpv_global *global,
}
ctx->native_ctx = native_ctx;
ctx->destroy_native_ctx = native_displays[n]->destroy;
- ctx->hwctx.destroy = va_destroy_ctx;
- return &ctx->hwctx;
+ return ctx->hwctx.av_device_ref;
}
}
return NULL;
}
+
+const struct hwcontext_fns hwcontext_fns_vaapi = {
+ .av_hwdevice_type = AV_HWDEVICE_TYPE_VAAPI,
+ .create_dev = va_create_standalone,
+ .is_emulated = is_emulated,
+};
diff --git a/video/vaapi.h b/video/vaapi.h
index d21e3a3..b2c31d8 100644
--- a/video/vaapi.h
+++ b/video/vaapi.h
@@ -1,18 +1,18 @@
/*
* This file is part of mpv.
*
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * mpv 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.
*
* mpv 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPV_VAAPI_H
@@ -26,55 +26,27 @@
#include "mp_image.h"
#include "hwdec.h"
-struct mp_image_pool;
-struct mp_log;
-
struct mp_vaapi_ctx {
struct mp_hwdec_ctx hwctx;
struct mp_log *log;
VADisplay display;
struct AVBufferRef *av_device_ref; // AVVAAPIDeviceContext*
- struct va_image_formats *image_formats;
- bool gpu_memcpy_message;
// Internal, for va_create_standalone()
void *native_ctx;
void (*destroy_native_ctx)(void *native_ctx);
};
-bool check_va_status(struct mp_log *log, VAStatus status, const char *msg);
-
-#define CHECK_VA_STATUS(ctx, msg) check_va_status((ctx)->log, status, msg)
+#define CHECK_VA_STATUS(ctx, msg) \
+ (status == VA_STATUS_SUCCESS ? true \
+ : (MP_ERR(ctx, "%s failed (%s)\n", msg, vaErrorStr(status)), false))
int va_get_colorspace_flag(enum mp_csp csp);
struct mp_vaapi_ctx * va_initialize(VADisplay *display, struct mp_log *plog, bool probing);
void va_destroy(struct mp_vaapi_ctx *ctx);
-enum mp_imgfmt va_fourcc_to_imgfmt(uint32_t fourcc);
-uint32_t va_fourcc_from_imgfmt(int imgfmt);
-VAImageFormat * va_image_format_from_imgfmt(struct mp_vaapi_ctx *ctx, int imgfmt);
-bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image, struct mp_image *mpi);
-bool va_image_unmap(struct mp_vaapi_ctx *ctx, VAImage *image);
-
-void va_surface_get_uncropped_size(struct mp_image *mpi, int *out_w, int *out_h);
-
-void va_pool_set_allocator(struct mp_image_pool *pool, struct mp_vaapi_ctx *ctx,
- int rt_format);
-
VASurfaceID va_surface_id(struct mp_image *mpi);
-int va_surface_rt_format(struct mp_image *mpi);
-struct mp_image *va_surface_download(struct mp_image *src,
- struct mp_image_pool *pool);
-
-int va_surface_alloc_imgfmt(struct mp_image *img, int imgfmt);
-int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src);
-
-void va_surface_init_subformat(struct mp_image *mpi);
bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx);
-struct mpv_global;
-struct mp_hwdec_ctx *va_create_standalone(struct mpv_global *global,
- struct mp_log *plog, bool probing);
-
#endif
diff --git a/video/vdpau.c b/video/vdpau.c
index b079262..6e3d0ac 100644
--- a/video/vdpau.c
+++ b/video/vdpau.c
@@ -31,89 +31,6 @@
#include "mp_image_pool.h"
#include "vdpau_mixer.h"
-static struct mp_image *download_image_yuv(struct mp_hwdec_ctx *hwctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool)
-{
- if (mpi->imgfmt != IMGFMT_VDPAU || mp_vdpau_mixed_frame_get(mpi))
- return NULL;
-
- return mp_image_hw_download(mpi, swpool);
-}
-
-static struct mp_image *download_image(struct mp_hwdec_ctx *hwctx,
- struct mp_image *mpi,
- struct mp_image_pool *swpool)
-{
- if (mpi->imgfmt != IMGFMT_VDPAU && mpi->imgfmt != IMGFMT_VDPAU_OUTPUT)
- return NULL;
-
- struct mp_vdpau_ctx *ctx = hwctx->ctx;
- struct vdp_functions *vdp = &ctx->vdp;
- VdpStatus vdp_st;
-
- struct mp_image *res = NULL;
- int w, h;
- mp_image_params_get_dsize(&mpi->params, &w, &h);
-
- res = download_image_yuv(hwctx, mpi, swpool);
- if (res)
- return res;
-
- // Abuse this lock for our own purposes. It could use its own lock instead.
- pthread_mutex_lock(&ctx->pool_lock);
-
- if (ctx->getimg_surface == VDP_INVALID_HANDLE ||
- ctx->getimg_w < w || ctx->getimg_h < h)
- {
- if (ctx->getimg_surface != VDP_INVALID_HANDLE) {
- vdp_st = vdp->output_surface_destroy(ctx->getimg_surface);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
- }
- ctx->getimg_surface = VDP_INVALID_HANDLE;
- vdp_st = vdp->output_surface_create(ctx->vdp_device,
- VDP_RGBA_FORMAT_B8G8R8A8, w, h,
- &ctx->getimg_surface);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_create");
- if (vdp_st != VDP_STATUS_OK)
- goto error;
- ctx->getimg_w = w;
- ctx->getimg_h = h;
- }
-
- if (!ctx->getimg_mixer)
- ctx->getimg_mixer = mp_vdpau_mixer_create(ctx, ctx->log);
-
- VdpRect in = { .x1 = mpi->w, .y1 = mpi->h };
- VdpRect out = { .x1 = w, .y1 = h };
- if (mp_vdpau_mixer_render(ctx->getimg_mixer, NULL, ctx->getimg_surface, &out,
- mpi, &in) < 0)
- goto error;
-
- res = mp_image_pool_get(swpool, IMGFMT_BGR0, ctx->getimg_w, ctx->getimg_h);
- if (!res)
- goto error;
-
- void *dst_planes[] = { res->planes[0] };
- uint32_t dst_pitches[] = { res->stride[0] };
- vdp_st = vdp->output_surface_get_bits_native(ctx->getimg_surface, NULL,
- dst_planes, dst_pitches);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_get_bits_native");
- if (vdp_st != VDP_STATUS_OK)
- goto error;
-
- mp_image_set_size(res, w, h);
- mp_image_copy_attributes(res, mpi);
-
- pthread_mutex_unlock(&ctx->pool_lock);
- return res;
-error:
- talloc_free(res);
- MP_WARN(ctx, "Error copying image from GPU.\n");
- pthread_mutex_unlock(&ctx->pool_lock);
- return NULL;
-}
-
static void mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx *ctx)
{
for (int i = 0; i < MAX_VIDEO_SURFACES; i++) {
@@ -413,62 +330,85 @@ struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx,
return mp_vdpau_get_surface(ctx, chroma, 0, false, w, h);
}
-static void recheck_preemption(struct mp_hwdec_ctx *hwctx)
+static void free_device_ref(struct AVHWDeviceContext *hwctx)
{
- struct mp_vdpau_ctx *ctx = hwctx->ctx;
+ struct mp_vdpau_ctx *ctx = hwctx->user_opaque;
- mp_vdpau_handle_preemption(ctx, NULL);
-}
-
-static bool open_lavu_vdpau_device(struct mp_vdpau_ctx *ctx)
-{
- ctx->av_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VDPAU);
- if (!ctx->av_device_ref)
- return false;
+ struct vdp_functions *vdp = &ctx->vdp;
+ VdpStatus vdp_st;
- AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data;
- AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
+ for (int i = 0; i < MAX_VIDEO_SURFACES; i++) {
+ // can't hold references past context lifetime
+ assert(!ctx->video_surfaces[i].in_use);
+ if (ctx->video_surfaces[i].surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->video_surface_destroy(ctx->video_surfaces[i].surface);
+ CHECK_VDP_WARNING(ctx, "Error when calling vdp_video_surface_destroy");
+ }
+ if (ctx->video_surfaces[i].osurface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(ctx->video_surfaces[i].osurface);
+ CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
+ }
+ }
- vdctx->device = ctx->vdp_device;
- vdctx->get_proc_address = ctx->get_proc_address;
+ if (ctx->preemption_obj != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(ctx->preemption_obj);
+ CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
+ }
- if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0)
- av_buffer_unref(&ctx->av_device_ref);
+ if (vdp->device_destroy && ctx->vdp_device != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->device_destroy(ctx->vdp_device);
+ CHECK_VDP_WARNING(ctx, "Error when calling vdp_device_destroy");
+ }
- ctx->hwctx.av_device_ref = ctx->av_device_ref;
+ if (ctx->close_display)
+ XCloseDisplay(ctx->x11);
- return !!ctx->av_device_ref;
+ pthread_mutex_destroy(&ctx->pool_lock);
+ pthread_mutex_destroy(&ctx->preempt_lock);
+ talloc_free(ctx);
}
struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11,
bool probing)
{
+ AVBufferRef *avref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VDPAU);
+ if (!avref)
+ return NULL;
+
+ AVHWDeviceContext *hwctx = (void *)avref->data;
+ AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
+
struct mp_vdpau_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct mp_vdpau_ctx) {
.log = log,
.x11 = x11,
.preemption_counter = 1,
+ .av_device_ref = avref,
.hwctx = {
- .type = HWDEC_VDPAU,
- .ctx = ctx,
- .download_image = download_image,
- .restore_device = recheck_preemption,
+ .av_device_ref = avref,
},
- .getimg_surface = VDP_INVALID_HANDLE,
};
mpthread_mutex_init_recursive(&ctx->preempt_lock);
pthread_mutex_init(&ctx->pool_lock, NULL);
+ hwctx->free = free_device_ref;
+ hwctx->user_opaque = ctx;
+
mark_vdpau_objects_uninitialized(ctx);
if (win_x11_init_vdpau_procs(ctx, probing) < 0) {
mp_vdpau_destroy(ctx);
return NULL;
}
- if (!open_lavu_vdpau_device(ctx)) {
+
+ vdctx->device = ctx->vdp_device;
+ vdctx->get_proc_address = ctx->get_proc_address;
+
+ if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0) {
mp_vdpau_destroy(ctx);
return NULL;
}
+
return ctx;
}
@@ -477,44 +417,8 @@ void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx)
if (!ctx)
return;
- struct vdp_functions *vdp = &ctx->vdp;
- VdpStatus vdp_st;
-
- for (int i = 0; i < MAX_VIDEO_SURFACES; i++) {
- // can't hold references past context lifetime
- assert(!ctx->video_surfaces[i].in_use);
- if (ctx->video_surfaces[i].surface != VDP_INVALID_HANDLE) {
- vdp_st = vdp->video_surface_destroy(ctx->video_surfaces[i].surface);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_video_surface_destroy");
- }
- if (ctx->video_surfaces[i].osurface != VDP_INVALID_HANDLE) {
- vdp_st = vdp->output_surface_destroy(ctx->video_surfaces[i].osurface);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
- }
- }
-
- if (ctx->getimg_mixer)
- mp_vdpau_mixer_destroy(ctx->getimg_mixer);
- if (ctx->getimg_surface != VDP_INVALID_HANDLE) {
- vdp_st = vdp->output_surface_destroy(ctx->getimg_surface);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
- }
-
- av_buffer_unref(&ctx->av_device_ref);
-
- if (ctx->preemption_obj != VDP_INVALID_HANDLE) {
- vdp_st = vdp->output_surface_destroy(ctx->preemption_obj);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy");
- }
-
- if (vdp->device_destroy && ctx->vdp_device != VDP_INVALID_HANDLE) {
- vdp_st = vdp->device_destroy(ctx->vdp_device);
- CHECK_VDP_WARNING(ctx, "Error when calling vdp_device_destroy");
- }
-
- pthread_mutex_destroy(&ctx->pool_lock);
- pthread_mutex_destroy(&ctx->preempt_lock);
- talloc_free(ctx);
+ AVBufferRef *ref = ctx->av_device_ref;
+ av_buffer_unref(&ref); // frees ctx as well
}
bool mp_vdpau_get_format(int imgfmt, VdpChromaType *out_chroma_type,
@@ -623,16 +527,28 @@ bool mp_vdpau_guess_if_emulated(struct mp_vdpau_ctx *ctx)
return vdp_st == VDP_STATUS_OK && info && strstr(info, "VAAPI");
}
-static void vdpau_destroy_standalone(struct mp_hwdec_ctx *ctx)
+// (This clearly works only for contexts wrapped by our code.)
+struct mp_vdpau_ctx *mp_vdpau_get_ctx_from_av(AVBufferRef *hw_device_ctx)
+{
+ AVHWDeviceContext *hwctx = (void *)hw_device_ctx->data;
+
+ if (hwctx->free != free_device_ref)
+ return NULL; // not ours
+
+ return hwctx->user_opaque;
+}
+
+static bool is_emulated(struct AVBufferRef *hw_device_ctx)
{
- struct mp_vdpau_ctx *vdp = ctx->ctx;
- Display *display = vdp->x11;
- mp_vdpau_destroy(vdp);
- XCloseDisplay(display);
+ struct mp_vdpau_ctx *ctx = mp_vdpau_get_ctx_from_av(hw_device_ctx);
+ if (!ctx)
+ return false;
+
+ return mp_vdpau_guess_if_emulated(ctx);
}
-struct mp_hwdec_ctx *vdpau_create_standalone(struct mpv_global *global,
- struct mp_log *plog, bool probing)
+static struct AVBufferRef *vdpau_create_standalone(struct mpv_global *global,
+ struct mp_log *log, struct hwcontext_create_dev_params *params)
{
XInitThreads();
@@ -640,13 +556,19 @@ struct mp_hwdec_ctx *vdpau_create_standalone(struct mpv_global *global,
if (!display)
return NULL;
- struct mp_vdpau_ctx *vdp = mp_vdpau_create_device_x11(plog, display, probing);
+ struct mp_vdpau_ctx *vdp =
+ mp_vdpau_create_device_x11(log, display, params->probing);
if (!vdp) {
XCloseDisplay(display);
return NULL;
}
- vdp->hwctx.emulated = mp_vdpau_guess_if_emulated(vdp);
- vdp->hwctx.destroy = vdpau_destroy_standalone;
- return &vdp->hwctx;
+ vdp->close_display = true;
+ return vdp->hwctx.av_device_ref;
}
+
+const struct hwcontext_fns hwcontext_fns_vdpau = {
+ .av_hwdevice_type = AV_HWDEVICE_TYPE_VDPAU,
+ .create_dev = vdpau_create_standalone,
+ .is_emulated = is_emulated,
+};
diff --git a/video/vdpau.h b/video/vdpau.h
index b320fa5..b0f402c 100644
--- a/video/vdpau.h
+++ b/video/vdpau.h
@@ -12,6 +12,11 @@
#include "common/msg.h"
#include "hwdec.h"
+#include "config.h"
+#if !HAVE_GPL
+#error GPL only
+#endif
+
#define CHECK_VDP_ERROR_ST(ctx, message, statement) \
do { \
if (vdp_st != VDP_STATUS_OK) { \
@@ -46,6 +51,7 @@ struct vdp_functions {
struct mp_vdpau_ctx {
struct mp_log *log;
Display *x11;
+ bool close_display;
struct mp_hwdec_ctx hwctx;
struct AVBufferRef *av_device_ref;
@@ -79,19 +85,12 @@ struct mp_vdpau_ctx {
bool in_use;
int64_t age;
} video_surfaces[MAX_VIDEO_SURFACES];
- struct mp_vdpau_mixer *getimg_mixer;
- VdpOutputSurface getimg_surface;
- int getimg_w, getimg_h;
};
struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11,
bool probing);
void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx);
-struct mpv_global;
-struct mp_hwdec_ctx *vdpau_create_standalone(struct mpv_global *global,
- struct mp_log *plog, bool probing);
-
int mp_vdpau_handle_preemption(struct mp_vdpau_ctx *ctx, uint64_t *counter);
struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx,
@@ -104,6 +103,8 @@ bool mp_vdpau_get_rgb_format(int imgfmt, VdpRGBAFormat *out_rgba_format);
struct mp_image *mp_vdpau_upload_video_surface(struct mp_vdpau_ctx *ctx,
struct mp_image *mpi);
+struct mp_vdpau_ctx *mp_vdpau_get_ctx_from_av(struct AVBufferRef *hw_device_ctx);
+
bool mp_vdpau_guess_if_emulated(struct mp_vdpau_ctx *ctx);
#endif
diff --git a/video/vt.c b/video/vt.c
deleted file mode 100644
index 8488256..0000000
--- a/video/vt.c
+++ /dev/null
@@ -1,74 +0,0 @@
-#include <CoreVideo/CoreVideo.h>
-
-#include "video/decode/lavc.h"
-
-#include "mp_image.h"
-#include "mp_image_pool.h"
-#include "vt.h"
-
-static const uint32_t map_imgfmt_cvpixfmt[][2] = {
- {IMGFMT_420P, kCVPixelFormatType_420YpCbCr8Planar},
- {IMGFMT_UYVY, kCVPixelFormatType_422YpCbCr8},
- {IMGFMT_NV12, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange},
- {0}
-};
-
-uint32_t mp_imgfmt_to_cvpixelformat(int mpfmt)
-{
- for (int n = 0; map_imgfmt_cvpixfmt[n][0]; n++) {
- if (map_imgfmt_cvpixfmt[n][0] == mpfmt)
- return map_imgfmt_cvpixfmt[n][1];
- }
- return 0;
-}
-
-int mp_imgfmt_from_cvpixelformat(uint32_t cvpixfmt)
-{
- for (int n = 0; map_imgfmt_cvpixfmt[n][0]; n++) {
- if (map_imgfmt_cvpixfmt[n][1] == cvpixfmt)
- return map_imgfmt_cvpixfmt[n][0];
- }
- return 0;
-}
-
-// (ctx is unused - it's for compatibility with mp_hwdec_ctx.download_image())
-struct mp_image *mp_vt_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *hw_image,
- struct mp_image_pool *swpool)
-{
- if (hw_image->imgfmt != IMGFMT_VIDEOTOOLBOX)
- return NULL;
-
- struct mp_image *image = NULL;
- CVPixelBufferRef pbuf = (CVPixelBufferRef)hw_image->planes[3];
- CVPixelBufferLockBaseAddress(pbuf, kCVPixelBufferLock_ReadOnly);
- size_t width = CVPixelBufferGetWidth(pbuf);
- size_t height = CVPixelBufferGetHeight(pbuf);
- uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(pbuf);
- int imgfmt = mp_imgfmt_from_cvpixelformat(cvpixfmt);
- if (!imgfmt)
- goto unlock;
-
- struct mp_image img = {0};
- mp_image_setfmt(&img, imgfmt);
- mp_image_set_size(&img, width, height);
-
- if (CVPixelBufferIsPlanar(pbuf)) {
- int planes = CVPixelBufferGetPlaneCount(pbuf);
- for (int i = 0; i < planes; i++) {
- img.planes[i] = CVPixelBufferGetBaseAddressOfPlane(pbuf, i);
- img.stride[i] = CVPixelBufferGetBytesPerRowOfPlane(pbuf, i);
- }
- } else {
- img.planes[0] = CVPixelBufferGetBaseAddress(pbuf);
- img.stride[0] = CVPixelBufferGetBytesPerRow(pbuf);
- }
-
- mp_image_copy_attributes(&img, hw_image);
-
- image = mp_image_pool_new_copy(swpool, &img);
-
-unlock:
- CVPixelBufferUnlockBaseAddress(pbuf, kCVPixelBufferLock_ReadOnly);
- return image;
-}
diff --git a/video/vt.h b/video/vt.h
deleted file mode 100644
index e488f29..0000000
--- a/video/vt.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef MPV_VT_H
-#define MPV_VT_H
-
-#include <stdint.h>
-
-int mp_imgfmt_from_cvpixelformat(uint32_t cvpixfmt);
-uint32_t mp_imgfmt_to_cvpixelformat(int mpfmt);
-
-struct mp_image;
-struct mp_image_pool;
-struct mp_hwdec_ctx;
-struct mp_image *mp_vt_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *hw_image,
- struct mp_image_pool *swpool);
-
-#endif
diff --git a/waftools/checks/custom.py b/waftools/checks/custom.py
index d8065a3..6987424 100644
--- a/waftools/checks/custom.py
+++ b/waftools/checks/custom.py
@@ -4,7 +4,7 @@ from waflib import Utils
import os
__all__ = ["check_pthreads", "check_iconv", "check_lua",
- "check_cocoa", "check_openal"]
+ "check_cocoa", "check_openal", "check_wl_protocols"]
pthreads_program = load_fragment('pthreads.c')
@@ -83,6 +83,15 @@ def check_lua(ctx, dependency_identifier):
return True
return False
+def check_wl_protocols(ctx, dependency_identifier):
+ def fn(ctx, dependency_identifier):
+ ret = check_pkg_config_datadir("wayland-protocols")
+ ret = ret(ctx, dependency_identifier)
+ if ret != None:
+ ctx.env.WL_PROTO_DIR = ret.split()[0]
+ return ret
+ return fn(ctx, dependency_identifier)
+
def check_cocoa(ctx, dependency_identifier):
fn = check_cc(
fragment = load_fragment('cocoa.m'),
diff --git a/waftools/checks/generic.py b/waftools/checks/generic.py
index 153cf94..093c600 100644
--- a/waftools/checks/generic.py
+++ b/waftools/checks/generic.py
@@ -7,7 +7,8 @@ __all__ = [
"check_pkg_config", "check_pkg_config_mixed", "check_pkg_config_mixed_all",
"check_pkg_config_cflags", "check_cc", "check_statement", "check_libs",
"check_headers", "compose_checks", "check_true", "any_version",
- "load_fragment", "check_stub", "check_ctx_vars", "check_program"]
+ "load_fragment", "check_stub", "check_ctx_vars", "check_program",
+ "check_pkg_config_datadir"]
any_version = None
@@ -82,6 +83,9 @@ def check_pkg_config_mixed_all(*all_args, **kw_ext):
def check_pkg_config_cflags(*args, **kw_ext):
return _check_pkg_config([], ["--cflags"], *args, **kw_ext)
+def check_pkg_config_datadir(*args, **kw_ext):
+ return _check_pkg_config([], ["--variable=pkgdatadir"], *args, **kw_ext)
+
def _check_pkg_config(_dyn_libs, _pkgc_args, *args, **kw_ext):
def fn(ctx, dependency_identifier, **kw):
argsl = list(args)
@@ -113,7 +117,7 @@ def _check_pkg_config(_dyn_libs, _pkgc_args, *args, **kw_ext):
# added only at its first occurrence.
original_append_unique = ConfigSet.append_unique
ConfigSet.append_unique = ConfigSet.append_value
- result = bool(ctx.check_cfg(**opts))
+ result = ctx.check_cfg(**opts)
ConfigSet.append_unique = original_append_unique
defkey = inflector.define_key(dependency_identifier)
diff --git a/waftools/dependencies.py b/waftools/dependencies.py
index 994e1d1..3cef6a3 100644
--- a/waftools/dependencies.py
+++ b/waftools/dependencies.py
@@ -2,6 +2,7 @@ from waflib.Errors import ConfigurationError, WafError
from waflib.Configure import conf
from waflib.Build import BuildContext
from waflib.Logs import pprint
+import deps_parser
import inflector
class DependencyError(Exception):
@@ -16,11 +17,9 @@ class Dependency(object):
self.attributes = self.__parse_attributes__(dependency)
known_deps.add(self.identifier)
- for dep_key in ['deps', 'deps_any', 'deps_neg']:
- if dep_key in self.attributes:
- deps = self.attributes[dep_key]
- self.ctx.ensure_dependency_is_known(*deps)
+ if 'deps' in self.attributes:
+ self.ctx.ensure_dependency_is_known(self.attributes['deps'])
def __parse_attributes__(self, dependency):
if 'os_specific_checks' in dependency:
@@ -36,9 +35,7 @@ class Dependency(object):
try:
self.check_group_disabled()
self.check_disabled()
- self.check_any_dependencies()
self.check_dependencies()
- self.check_negative_dependencies()
except DependencyError:
# No check was run, since the prerequisites of the dependency are
# not satisfied. Make sure the define is 'undefined' so that we
@@ -67,27 +64,12 @@ class Dependency(object):
self.attributes['fmsg'] = "You manually enabled the feature '{0}', but \
the autodetection check failed.".format(self.identifier)
- def check_any_dependencies(self):
- if 'deps_any' in self.attributes:
- deps = set(self.attributes['deps_any'])
- if len(deps & self.satisfied_deps) == 0:
- self.skip("not found any of {0}".format(", ".join(deps)))
- raise DependencyError
-
def check_dependencies(self):
if 'deps' in self.attributes:
- deps = set(self.attributes['deps'])
- if not deps <= self.satisfied_deps:
- missing_deps = deps - self.satisfied_deps
- self.skip("{0} not found".format(", ".join(missing_deps)))
- raise DependencyError
-
- def check_negative_dependencies(self):
- if 'deps_neg' in self.attributes:
- deps = set(self.attributes['deps_neg'])
- conflicting_deps = deps & self.satisfied_deps
- if len(conflicting_deps) > 0:
- self.skip("{0} found".format(", ".join(conflicting_deps)), 'CYAN')
+ ok, why = deps_parser.check_dependency_expr(self.attributes['deps'],
+ self.satisfied_deps)
+ if not ok:
+ self.skip(why)
raise DependencyError
def check_autodetect_func(self):
@@ -145,13 +127,19 @@ def configure(ctx):
__detect_target_os_dependency__(ctx)
@conf
-def ensure_dependency_is_known(ctx, *depnames):
- deps = set([d for d in depnames if not d.startswith('os-')])
- if not deps <= ctx.known_deps:
- raise ConfigurationError(
- "error in dependencies definition: some dependencies in"
- " {0} are unknown.".format(deps))
-
+def ensure_dependency_is_known(ctx, depnames):
+ def check(ast):
+ if isinstance(ast, deps_parser.AstSym):
+ if (not ast.name.startswith('os-')) and ast.name not in ctx.known_deps:
+ raise ConfigurationError(
+ "error in dependencies definition: dependency {0} in"
+ " {1} is unknown.".format(ast.name, depnames))
+ elif isinstance(ast, deps_parser.AstOp):
+ for sub in ast.sub:
+ check(sub)
+ else:
+ assert False
+ check(deps_parser.parse_expr(depnames))
@conf
def mark_satisfied(ctx, dependency_identifier):
@@ -174,7 +162,9 @@ def parse_dependencies(ctx, dependencies):
@conf
def dependency_satisfied(ctx, dependency_identifier):
ctx.ensure_dependency_is_known(dependency_identifier)
- return dependency_identifier in ctx.satisfied_deps
+ ok, _ = deps_parser.check_dependency_expr(dependency_identifier,
+ ctx.satisfied_deps)
+ return ok
@conf
def store_dependencies_lists(ctx):
@@ -194,13 +184,7 @@ def filtered_sources(ctx, sources):
return source
def __check_filter__(dependency):
- if dependency.find('!') == 0:
- dependency = dependency.lstrip('!')
- ctx.ensure_dependency_is_known(dependency)
- return dependency not in ctx.satisfied_deps
- else:
- ctx.ensure_dependency_is_known(dependency)
- return dependency in ctx.satisfied_deps
+ return dependency_satisfied(ctx, dependency)
def __unpack_and_check_filter__(source):
try:
diff --git a/waftools/deps_parser.py b/waftools/deps_parser.py
new file mode 100644
index 0000000..eef3b6f
--- /dev/null
+++ b/waftools/deps_parser.py
@@ -0,0 +1,211 @@
+
+class ParseError(Exception):
+ pass
+
+class AstOp(object):
+ def __init__(self, op, sub):
+ self.op = op
+ self.sub = sub
+
+ def __repr__(self):
+ if len(self.sub) == 1:
+ return self.op + str(self.sub[0])
+ return "(" + (" " + self.op + " ").join([str(x) for x in self.sub]) + ")"
+
+class AstSym(object):
+ def __init__(self, name):
+ assert type(name) is type("")
+ self.name = name
+
+ def __repr__(self):
+ return self.name
+
+Arity = { "!": 1, "&&": 2, "||": 2 }
+Precedence = { "!": 3, "&&": 2, "||": 1 }
+Tokens = list(Arity.keys()) + ["(", ")"]
+
+# return (token, rest), or (None, "") if nothing left
+def read_tok(expr):
+ expr = expr.strip()
+ for t in Tokens:
+ if expr.startswith(t):
+ return (t, expr[len(t):])
+ if expr == "":
+ return (None, "")
+ sym = ""
+ while len(expr) and ((expr[0].lower() >= 'a' and expr[0].lower() <= 'z') or
+ (expr[0] >= '0' and expr[0] <= '9') or
+ (expr[0] in ["_", "-", "."])):
+ sym += expr[0]
+ expr = expr[1:]
+ if len(sym):
+ return sym, expr
+ raise ParseError("unknown token in '%s'" % expr)
+
+def parse_expr(expr):
+ opstack = []
+ outstack = []
+ def out(sym):
+ if sym in Arity:
+ sub = []
+ for i in range(Arity[sym]):
+ if len(outstack) == 0:
+ raise ParseError("missing operator argument")
+ sub.insert(0, outstack.pop())
+ outstack.append(AstOp(sym, sub))
+ elif sym == "(":
+ raise ParseError("missing closing ')'")
+ elif not isinstance(sym, AstSym):
+ raise ParseError("bogus symbol '%s'" % sym)
+ else:
+ outstack.append(sym)
+ while True:
+ tok, expr = read_tok(expr)
+ if tok is None:
+ break
+ if tok in Arity:
+ while len(opstack) and opstack[-1] != '(' and \
+ Precedence[opstack[-1]] > Precedence[tok]:
+ out(opstack.pop())
+ opstack.append(tok)
+ elif tok == "(":
+ opstack.append(tok)
+ elif tok == ")":
+ while True:
+ if not len(opstack):
+ raise ParseError("missing '(' for ')'")
+ sym = opstack.pop()
+ if sym == "(":
+ break
+ out(sym)
+ else:
+ out(AstSym(tok)) # Assume a terminal
+ while len(opstack):
+ out(opstack.pop())
+ if len(outstack) != 1:
+ raise ParseError("empty expression or extra symbols (%s)" % outstack)
+ return outstack.pop()
+
+def convert_dnf(ast):
+
+ # no nested ! (negation normal form)
+ def simplify_negation(ast):
+ if isinstance(ast, AstOp):
+ if ast.op == "!":
+ sub = ast.sub[0]
+ if isinstance(sub, AstOp):
+ if sub.op == "!":
+ return sub.sub[0]
+ elif sub.op in ["&&", "||"]:
+ sub.op = "||" if sub.op == "&&" else "&&"
+ sub.sub = [AstOp("!", [x]) for x in sub.sub]
+ return simplify_negation(sub)
+ else:
+ ast.sub = [simplify_negation(x) for x in ast.sub]
+ return ast
+
+ # a && (b && c) => a && b && c
+ def flatten(ast):
+ if isinstance(ast, AstOp):
+ can_flatten = ast.op in ["&&", "||"]
+ nsub = []
+ for sub in ast.sub:
+ sub = flatten(sub)
+ if isinstance(sub, AstOp) and sub.op == ast.op and can_flatten:
+ nsub.extend(sub.sub)
+ else:
+ nsub.append(sub)
+ ast.sub = nsub
+ if len(ast.sub) == 1 and can_flatten:
+ return ast.sub[0]
+ return ast
+
+ # a && (b || c) && d => (a && d && b) || (a && d && c)
+ def redist(ast):
+ def recombine(a, stuff):
+ return AstOp("||", [AstOp("&&", [a, n]) for n in stuff])
+ if isinstance(ast, AstOp):
+ ast.sub = [flatten(redist(x)) for x in ast.sub]
+ if ast.op == "&&":
+ for sub in ast.sub:
+ if isinstance(sub, AstOp) and sub.op == "||":
+ if len(ast.sub) == 1:
+ return redist(sub)
+ other = None
+ for n in ast.sub:
+ if n is not sub:
+ if other is None:
+ other = n
+ else:
+ other = flatten(AstOp("&&", [other, n]))
+ return flatten(redist(recombine(other, sub.sub)))
+ return ast
+
+ return redist(flatten(simplify_negation(ast)))
+
+# Returns (success_as_bool, failure_reason_as_string)
+def check_dependency_expr(expr, deps):
+ ast = parse_expr(expr)
+ def eval_ast(ast):
+ if isinstance(ast, AstSym):
+ return ast.name in deps
+ elif isinstance(ast, AstOp):
+ vals = [eval_ast(x) for x in ast.sub]
+ if ast.op == "&&":
+ return vals[0] and vals[1]
+ elif ast.op == "||":
+ return vals[0] or vals[1]
+ elif ast.op == "!":
+ return not vals[0]
+ assert False
+ if eval_ast(ast):
+ return True, None
+
+ # Now the same thing again, but more complicated, and informing what is
+ # missing.
+ ast = convert_dnf(ast)
+
+ # ast now is a or-combined list of and-combined deps. Each dep can have a
+ # negation (marking a conflict). Each case of and-combined deps is a way
+ # to satisfy the deps expression. Instead of dumping full information,
+ # distinguish the following cases, and only mention the one that applies,
+ # in order:
+ # 1. the first missing dep of a case that has missing deps only
+ # 2. the first conflicting dep at all
+
+ def get_sub_list(node, op):
+ if isinstance(node, AstOp) and node.op == op:
+ return node.sub
+ else:
+ return [node]
+
+ conflict_dep = None
+ missing_dep = None
+
+ for group in get_sub_list(ast, "||"):
+ group_conflict = None
+ group_missing_dep = None
+ for elem in get_sub_list(group, "&&"):
+ neg = False
+ if isinstance(elem, AstOp) and elem.op == "!":
+ neg = True
+ elem = elem.sub[0]
+ if not isinstance(elem, AstSym):
+ continue # broken DNF?
+ name = elem.name
+ present = name in deps
+ if (not present) and (not neg) and (group_missing_dep is None):
+ group_missing_dep = name
+ if present and neg and (group_conflict is None):
+ group_conflict = name
+ if (missing_dep is None) and (group_conflict is None):
+ missing_dep = group_missing_dep
+ if conflict_dep is None:
+ conflict_dep = group_conflict
+
+ reason = "unknown"
+ if missing_dep is not None:
+ reason = "%s not found" % (missing_dep)
+ elif conflict_dep is not None:
+ reason = "%s found" % (conflict_dep)
+ return False, reason
diff --git a/waftools/detections/compiler.py b/waftools/detections/compiler.py
index 7033cf5..1befec5 100644
--- a/waftools/detections/compiler.py
+++ b/waftools/detections/compiler.py
@@ -52,7 +52,7 @@ def __add_clang_flags__(ctx):
"-Wno-tautological-constant-out-of-range-compare" ]
def __add_mswin_flags__(ctx):
- ctx.env.CFLAGS += ['-D_WIN32_WINNT=0x0601', '-DUNICODE', '-DCOBJMACROS',
+ ctx.env.CFLAGS += ['-D_WIN32_WINNT=0x0602', '-DUNICODE', '-DCOBJMACROS',
'-DINITGUID', '-U__STRICT_ANSI__']
ctx.env.LAST_LINKFLAGS += ['-Wl,--major-os-version=6,--minor-os-version=0',
'-Wl,--major-subsystem-version=6,--minor-subsystem-version=0']
diff --git a/waftools/fragments/cuda.c b/waftools/fragments/cuda.c
index c63ec29..1d534f6 100644
--- a/waftools/fragments/cuda.c
+++ b/waftools/fragments/cuda.c
@@ -2,11 +2,14 @@
typedef void * CUcontext;
+#include <libavcodec/avcodec.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_cuda.h>
int main(int argc, char *argv[]) {
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_CUDA;
AVCUDADeviceContextInternal *foo;
+ AVCodecContext *avctx = avcodec_alloc_context3(NULL);
+ avctx->hw_device_ctx = NULL;
return 0;
}
diff --git a/waftools/generators/headers.py b/waftools/generators/headers.py
index c7f5b48..84f914c 100644
--- a/waftools/generators/headers.py
+++ b/waftools/generators/headers.py
@@ -29,7 +29,6 @@ def __add_mpv_defines__(ctx):
ctx.define("CONFIGURATION", " ".join(argv))
ctx.define("MPV_CONFDIR", ctx.env.CONFLOADDIR)
ctx.define("FULLCONFIG", __escape_c_string(__get_features_string__(ctx)))
- ctx.define("HAVE_GPL", 1)
def configure(ctx):
__add_mpv_defines__(ctx)
diff --git a/waftools/generators/sources.py b/waftools/generators/sources.py
index 96224cf..b0b423e 100644
--- a/waftools/generators/sources.py
+++ b/waftools/generators/sources.py
@@ -9,6 +9,9 @@ def __zshcomp_cmd__(ctx, argument):
return '"${{BIN_PERL}}" "{0}/TOOLS/zsh.pl" "{1}" > "${{TGT}}"' \
.format(ctx.srcnode.abspath(), argument)
+def __wayland_scanner_cmd__(ctx, mode, dir, src):
+ return "${{WAYSCAN}} {0} < {1}/{2} > ${{TGT}}".format(mode, dir, src)
+
def __file2string__(ctx, **kwargs):
ctx(
rule = __file2string_cmd__(ctx),
@@ -51,5 +54,24 @@ def __zshcomp__(ctx, **kwargs):
**kwargs
)
-BuildContext.file2string = __file2string__
-BuildContext.zshcomp = __zshcomp__
+def __wayland_protocol_code__(ctx, **kwargs):
+ ctx(
+ rule = __wayland_scanner_cmd__(ctx, 'code', kwargs['proto_dir'],
+ kwargs['protocol'] + '.xml'),
+ name = os.path.basename(kwargs['target']),
+ **kwargs
+ )
+
+def __wayland_protocol_header__(ctx, **kwargs):
+ ctx(
+ rule = __wayland_scanner_cmd__(ctx, 'client-header', kwargs['proto_dir'],
+ kwargs['protocol'] + '.xml'),
+ before = ('c',),
+ name = os.path.basename(kwargs['target']),
+ **kwargs
+ )
+
+BuildContext.file2string = __file2string__
+BuildContext.wayland_protocol_code = __wayland_protocol_code__
+BuildContext.wayland_protocol_header = __wayland_protocol_header__
+BuildContext.zshcomp = __zshcomp__
diff --git a/waftools/syms.py b/waftools/syms.py
index eff35e7..3ae69c6 100644
--- a/waftools/syms.py
+++ b/waftools/syms.py
@@ -33,7 +33,7 @@ class compile_sym(Task):
raise WafError('NotImplemented')
@feature('syms')
-@after_method('process_source', 'process_use', 'apply_link', 'process_uselib_local')
+@after_method('process_source', 'process_use', 'apply_link', 'process_uselib_local', 'propagate_uselib_vars')
def do_the_symbol_stuff(self):
tsk = self.create_task('compile_sym',
[self.path.find_node(self.export_symbols_def)],
diff --git a/wscript b/wscript
index 9d88588..750ce3b 100644
--- a/wscript
+++ b/wscript
@@ -14,7 +14,7 @@ c_preproc.standard_includes.append('/usr/local/include')
"""
Dependency identifiers (for win32 vs. Unix):
- wscript / C source meaning
+ wscript / C source meaning
--------------------------------------------------------------------------
posix / HAVE_POSIX: defined on Linux, OSX, Cygwin
(Cygwin emulates POSIX APIs on Windows)
@@ -27,6 +27,21 @@ Dependency identifiers (for win32 vs. Unix):
build_options = [
{
+ 'name': '--lgpl',
+ 'desc': 'LGPL (version 2.1 or later) build',
+ 'default': 'disable',
+ 'func': check_true,
+ }, {
+ 'name': 'gpl',
+ 'desc': 'GPL (version 2 or later) build',
+ 'deps': '!lgpl',
+ 'func': check_true,
+ }, {
+ 'name': 'libaf',
+ 'desc': 'internal audio filter chain',
+ 'deps': 'gpl',
+ 'func': check_true,
+ }, {
'name': '--cplayer',
'desc': 'mpv CLI player',
'default': 'enable',
@@ -40,7 +55,7 @@ build_options = [
'name': '--libmpv-static',
'desc': 'static library',
'default': 'disable',
- 'deps_neg': [ 'libmpv-shared' ],
+ 'deps': '!libmpv-shared',
'func': check_true
}, {
'name': '--static-build',
@@ -83,8 +98,7 @@ build_options = [
}, {
'name': '--cplugins',
'desc': 'C plugins',
- 'deps': [ 'libdl' ],
- 'deps_neg': [ 'os-win32' ],
+ 'deps': 'libdl && !os-win32',
'func': check_cc(linkflags=['-rdynamic']),
}, {
'name': '--zsh-comp',
@@ -127,7 +141,7 @@ main_dependencies = [
}, {
'name': 'mingw',
'desc': 'MinGW',
- 'deps': [ 'os-win32' ],
+ 'deps': 'os-win32',
'func': check_statement('stdlib.h', 'int x = __MINGW32__;'
'int y = __MINGW64_VERSION_MAJOR'),
}, {
@@ -139,11 +153,15 @@ main_dependencies = [
}, {
'name': '--android',
'desc': 'Android environment',
- 'func': check_statement('android/api-level.h', '(void)__ANDROID__'), # arbitrary android-specific header
+ 'func': compose_checks(
+ check_statement('android/api-level.h', '(void)__ANDROID__'), # arbitrary android-specific header
+ check_cc(lib="android"),
+ check_cc(lib="EGL"),
+ )
}, {
'name': 'posix-or-mingw',
'desc': 'development environment',
- 'deps_any': [ 'posix', 'mingw' ],
+ 'deps': 'posix || mingw',
'func': check_true,
'req': True,
'fmsg': 'Unable to find either POSIX or MinGW-w64 environment, ' \
@@ -152,20 +170,17 @@ main_dependencies = [
'name': '--uwp',
'desc': 'Universal Windows Platform',
'default': 'disable',
- 'deps': [ 'os-win32', 'mingw' ],
- 'deps_neg': [ 'cplayer' ],
+ 'deps': 'os-win32 && mingw && !cplayer',
'func': check_cc(lib=['windowsapp']),
}, {
'name': 'win32-desktop',
'desc': 'win32 desktop APIs',
- 'deps_any': [ 'os-win32', 'os-cygwin' ],
- 'deps_neg': [ 'uwp' ],
- 'func': check_cc(lib=['winmm', 'gdi32', 'ole32', 'uuid', 'avrt', 'dwmapi']),
+ 'deps': '(os-win32 || os-cygwin) && !uwp',
+ 'func': check_cc(lib=['winmm', 'gdi32', 'ole32', 'uuid', 'avrt', 'dwmapi', 'version']),
}, {
'name': '--win32-internal-pthreads',
'desc': 'internal pthread wrapper for win32 (Vista+)',
- 'deps_neg': [ 'posix' ],
- 'deps': [ 'os-win32' ],
+ 'deps': 'os-win32 && !posix',
'func': check_true,
}, {
'name': 'pthreads',
@@ -189,11 +204,11 @@ main_dependencies = [
'desc': 'stdatomic.h support or slow emulation',
'func': check_true,
'req': True,
- 'deps_any': ['stdatomic', 'gnuc'],
+ 'deps': 'stdatomic || gnuc',
}, {
'name': 'librt',
'desc': 'linking with -lrt',
- 'deps': [ 'pthreads' ],
+ 'deps': 'pthreads',
'func': check_cc(lib='rt')
}, {
'name': '--iconv',
@@ -206,48 +221,43 @@ iconv support use --disable-iconv.",
}, {
'name': 'dos-paths',
'desc': 'w32/dos paths',
- 'deps_any': [ 'os-win32', 'os-cygwin' ],
+ 'deps': 'os-win32 || os-cygwin',
'func': check_true
}, {
- 'name': '--termios',
- 'desc': 'termios',
- 'func': check_headers('termios.h', 'sys/termios.h'),
- }, {
- 'name': '--shm',
- 'desc': 'shm',
- 'func': check_statement(['sys/types.h', 'sys/ipc.h', 'sys/shm.h'],
- 'shmget(0, 0, 0); shmat(0, 0, 0); shmctl(0, 0, 0)')
- }, {
- 'name': 'nanosleep',
- 'desc': 'nanosleep',
- 'func': check_statement('time.h', 'nanosleep(0,0)')
- }, {
- 'name': 'posix-spawn',
- 'desc': 'POSIX spawnp()/kill()',
+ 'name': 'posix-spawn-native',
+ 'desc': 'spawnp()/kill() POSIX support',
'func': check_statement(['spawn.h', 'signal.h'],
'posix_spawnp(0,0,0,0,0,0); kill(0,0)'),
- 'deps_neg': ['mingw'],
+ 'deps': '!mingw',
+ }, {
+ 'name': 'posix-spawn-android',
+ 'desc': 'spawnp()/kill() Android replacement',
+ 'func': check_true,
+ 'deps': 'android && !posix-spawn-native',
+ },{
+ 'name': 'posix-spawn',
+ 'desc': 'any spawnp()/kill() support',
+ 'deps': 'posix-spawn-native || posix-spawn-android',
+ 'func': check_true,
}, {
'name': 'win32-pipes',
'desc': 'Windows pipe support',
'func': check_true,
- 'deps': [ 'win32-desktop' ],
- 'deps_neg': [ 'posix' ],
+ 'deps': 'win32-desktop && !posix',
}, {
'name': 'glob-posix',
'desc': 'glob() POSIX support',
- 'deps_neg': [ 'os-win32', 'os-cygwin' ],
+ 'deps': '!(os-win32 || os-cygwin)',
'func': check_statement('glob.h', 'glob("filename", 0, 0, 0)'),
}, {
'name': 'glob-win32',
'desc': 'glob() win32 replacement',
- 'deps_neg': [ 'posix' ],
- 'deps_any': [ 'os-win32', 'os-cygwin' ],
+ 'deps': '!posix && (os-win32 || os-cygwin)',
'func': check_true
}, {
'name': 'glob',
'desc': 'any glob() support',
- 'deps_any': [ 'glob-posix', 'glob-win32' ],
+ 'deps': 'glob-posix || glob-win32',
'func': check_true,
}, {
'name': 'fchmod',
@@ -271,13 +281,13 @@ iconv support use --disable-iconv.",
}, {
'name': 'osx-thread-name',
'desc': 'OSX API for setting thread name',
- 'deps_neg': [ 'glibc-thread-name' ],
+ 'deps': '!glibc-thread-name',
'func': check_statement('pthread.h',
'pthread_setname_np("ducks")', use=['pthreads']),
}, {
'name': 'bsd-thread-name',
'desc': 'BSD API for setting thread name',
- 'deps_neg': [ 'glibc-thread-name', 'osx-thread-name' ],
+ 'deps': '!(glibc-thread-name || osx-thread-name)',
'func': check_statement('pthread.h',
'pthread_set_name_np(pthread_self(), "ducks")',
use=['pthreads']),
@@ -289,13 +299,13 @@ iconv support use --disable-iconv.",
}, {
'name': 'linux-fstatfs',
'desc': "Linux's fstatfs()",
- 'deps': [ 'os-linux' ],
+ 'deps': 'os-linux',
'func': check_statement('sys/vfs.h',
'struct statfs fs; fstatfs(0, &fs); fs.f_namelen')
}, {
'name': '--libsmbclient',
'desc': 'Samba support (makes mpv GPLv3)',
- 'deps': [ 'libdl' ],
+ 'deps': 'libdl && gpl',
'func': check_pkg_config('smbclient'),
'default': 'disable',
'module': 'input',
@@ -318,12 +328,12 @@ iconv support use --disable-iconv.",
}, {
'name': '--libass-osd',
'desc': 'libass OSD support',
- 'deps': [ 'libass' ],
+ 'deps': 'libass',
'func': check_true,
}, {
'name': 'dummy-osd',
'desc': 'dummy OSD support',
- 'deps_neg': [ 'libass-osd' ],
+ 'deps': '!libass-osd',
'func': check_true,
} , {
'name': '--zlib',
@@ -344,32 +354,36 @@ iconv support use --disable-iconv.",
}, {
'name': '--dvdread',
'desc': 'dvdread support',
+ 'deps': 'gpl',
'func': check_pkg_config('dvdread', '>= 4.1.0'),
'default': 'disable',
}, {
'name': '--dvdnav',
'desc': 'dvdnav support',
+ 'deps': 'gpl',
'func': check_pkg_config('dvdnav', '>= 4.2.0',
'dvdread', '>= 4.1.0'),
'default': 'disable',
}, {
'name': 'dvdread-common',
'desc': 'DVD/IFO support',
- 'deps_any': [ 'dvdread', 'dvdnav' ],
+ 'deps': 'gpl && (dvdread || dvdnav)',
'func': check_true,
}, {
'name': '--cdda',
'desc': 'cdda support (libcdio)',
+ 'deps': 'gpl',
'func': check_pkg_config('libcdio_paranoia'),
'default': 'disable',
}, {
'name': '--uchardet',
'desc': 'uchardet support',
- 'deps': [ 'iconv' ],
+ 'deps': 'iconv',
'func': check_pkg_config('uchardet'),
}, {
'name': '--rubberband',
'desc': 'librubberband support',
+ 'deps': 'libaf',
'func': check_pkg_config('rubberband', '>= 1.8.0'),
}, {
'name': '--lcms2',
@@ -383,46 +397,41 @@ iconv support use --disable-iconv.",
}, {
'name': '--vapoursynth-lazy',
'desc': 'VapourSynth filter bridge (Lazy Lua)',
- 'deps': ['lua'],
+ 'deps': 'lua',
'func': check_pkg_config('vapoursynth', '>= 24'),
}, {
'name': 'vapoursynth-core',
'desc': 'VapourSynth filter bridge (core)',
- 'deps_any': ['vapoursynth', 'vapoursynth-lazy'],
+ 'deps': 'vapoursynth || vapoursynth-lazy',
'func': check_true,
}, {
'name': '--libarchive',
'desc': 'libarchive wrapper for reading zip files and more',
'func': check_pkg_config('libarchive >= 3.0.0'),
- 'default': 'disable',
}
]
-ffmpeg_version = "3.2.2"
ffmpeg_pkg_config_checks = [
- 'libavutil', '>= 55.34.100',
- 'libavcodec', '>= 57.64.100',
- 'libavformat', '>= 57.56.100',
- 'libswscale', '>= 4.2.100',
- 'libavfilter', '>= 6.65.100',
- 'libswresample', '>= 2.3.100',
+ 'libavutil', '>= 56.6.100',
+ 'libavcodec', '>= 58.7.100',
+ 'libavformat', '>= 58.0.102',
+ 'libswscale', '>= 5.0.101',
+ 'libavfilter', '>= 7.0.101',
+ 'libswresample', '>= 3.0.100',
]
-libav_version = "12"
libav_pkg_config_checks = [
- 'libavutil', '>= 55.20.0',
- 'libavcodec', '>= 57.25.0',
- 'libavformat', '>= 57.7.0',
- 'libswscale', '>= 4.0.0',
- 'libavfilter', '>= 6.7.0',
- 'libavresample', '>= 3.0.0',
+ 'libavutil', '>= 56.6.0',
+ 'libavcodec', '>= 58.8.0',
+ 'libavformat', '>= 58.1.0',
+ 'libswscale', '>= 5.0.0',
+ 'libavfilter', '>= 7.0.0',
+ 'libavresample', '>= 4.0.0',
]
-libav_versions_string = "FFmpeg %s or Libav %s" % (ffmpeg_version, libav_version)
-
def check_ffmpeg_or_libav_versions():
def fn(ctx, dependency_identifier, **kw):
versions = ffmpeg_pkg_config_checks
- if ctx.dependency_satisfied('is_libav'):
+ if ctx.dependency_satisfied('libav'):
versions = libav_pkg_config_checks
return check_pkg_config(*versions)(ctx, dependency_identifier, **kw)
return fn
@@ -435,59 +444,35 @@ libav_dependencies = [
'req': True,
'fmsg': "FFmpeg/Libav development files not found.",
}, {
- 'name': 'is_ffmpeg',
+ 'name': 'ffmpeg',
'desc': 'libav* is FFmpeg',
# FFmpeg <=> LIBAVUTIL_VERSION_MICRO>=100
'func': check_statement('libavcodec/version.h',
'int x[LIBAVCODEC_VERSION_MICRO >= 100 ? 1 : -1]',
- use='libavcodec')
+ use='libavcodec'),
}, {
- # This check should always result in the opposite of is_ffmpeg.
+ # This check should always result in the opposite of ffmpeg-*.
# Run it to make sure is_ffmpeg didn't fail for some other reason than
# the actual version check.
- 'name': 'is_libav',
+ 'name': 'libav',
'desc': 'libav* is Libav',
# FFmpeg <=> LIBAVUTIL_VERSION_MICRO>=100
'func': check_statement('libavcodec/version.h',
'int x[LIBAVCODEC_VERSION_MICRO >= 100 ? -1 : 1]',
use='libavcodec')
}, {
- 'name': 'libav',
+ 'name': 'libav-any',
'desc': 'Libav/FFmpeg library versions',
- 'deps_any': [ 'is_ffmpeg', 'is_libav' ],
+ 'deps': 'ffmpeg || libav',
'func': check_ffmpeg_or_libav_versions(),
'req': True,
'fmsg': "Unable to find development files for some of the required \
-FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_string)
+FFmpeg/Libav libraries. Git master is recommended."
}, {
'name': '--libavdevice',
'desc': 'libavdevice',
'func': check_pkg_config('libavdevice', '>= 57.0.0'),
- }, {
- 'name': 'avutil-imgcpy-uc',
- 'desc': 'libavutil GPU memcpy for hardware decoding',
- 'func': check_statement('libavutil/imgutils.h',
- 'av_image_copy_uc_from(0,0,0,0,0,0,0)',
- use='libav'),
- }, {
- 'name': 'avutil-content-light-level',
- 'desc': 'libavutil content light level struct',
- 'func': check_statement('libavutil/frame.h',
- 'AV_FRAME_DATA_CONTENT_LIGHT_LEVEL',
- use='libav'),
- }, {
- 'name': 'avutil-icc-profile',
- 'desc': 'libavutil ICC profile side data',
- 'func': check_statement('libavutil/frame.h',
- 'AV_FRAME_DATA_ICC_PROFILE',
- use='libav'),
- }, {
- 'name': 'avutil-spherical',
- 'desc': 'libavutil spherical side data',
- 'func': check_statement('libavutil/spherical.h',
- 'AV_SPHERICAL_EQUIRECTANGULAR',
- use='libav'),
- },
+ }
]
audio_output_features = [
@@ -499,14 +484,14 @@ audio_output_features = [
}, {
'name': '--sdl1',
'desc': 'SDL (1.x)',
- 'deps_neg': [ 'sdl2' ],
+ 'deps': '!sdl2',
'func': check_pkg_config('sdl'),
'default': 'disable'
}, {
'name': '--oss-audio',
'desc': 'OSS',
'func': check_cc(header_name='sys/soundcard.h'),
- 'deps': [ 'posix' ],
+ 'deps': 'posix && gpl',
}, {
'name': '--rsound',
'desc': 'RSound audio output',
@@ -524,6 +509,7 @@ audio_output_features = [
}, {
'name': '--jack',
'desc': 'JACK audio output',
+ 'deps': 'gpl',
'func': check_pkg_config('jack'),
}, {
'name': '--openal',
@@ -547,14 +533,14 @@ audio_output_features = [
}, {
'name': '--audiounit',
'desc': 'AudioUnit output for iOS',
- 'deps': ['atomics'],
+ 'deps': 'atomics',
'func': check_cc(
fragment=load_fragment('audiounit.c'),
framework_name=['Foundation', 'AudioToolbox'])
}, {
'name': '--wasapi',
'desc': 'WASAPI audio output',
- 'deps_any': ['os-win32', 'os-cygwin'],
+ 'deps': 'os-win32 || os-cygwin',
'func': check_cc(fragment=load_fragment('wasapi.c')),
}
]
@@ -567,22 +553,37 @@ video_output_features = [
}, {
'name': '--drm',
'desc': 'DRM',
- 'deps': [ 'vt.h' ],
+ 'deps': 'vt.h',
'func': check_pkg_config('libdrm'),
}, {
+ 'name': '--drmprime',
+ 'desc': 'DRM Prime ffmpeg support',
+ 'func': check_statement('libavutil/pixfmt.h',
+ 'int i = AV_PIX_FMT_DRM_PRIME')
+ }, {
'name': '--gbm',
'desc': 'GBM',
- 'deps': [ 'gbm.h' ],
+ 'deps': 'gbm.h',
'func': check_pkg_config('gbm'),
} , {
+ 'name': '--wayland-scanner',
+ 'desc': 'wayland-scanner',
+ 'func': check_program('wayland-scanner', 'WAYSCAN')
+ } , {
+ 'name': '--wayland-protocols',
+ 'desc': 'wayland-protocols',
+ 'func': check_wl_protocols
+ } , {
'name': '--wayland',
'desc': 'Wayland',
+ 'deps': 'wayland-protocols && wayland-scanner',
'func': check_pkg_config('wayland-client', '>= 1.6.0',
'wayland-cursor', '>= 1.6.0',
'xkbcommon', '>= 0.3.0'),
} , {
'name': '--x11',
'desc': 'X11',
+ 'deps': 'gpl',
'func': check_pkg_config('x11', '>= 1.0.0',
'xscrnsaver', '>= 1.0.0',
'xext', '>= 1.0.0',
@@ -591,12 +592,12 @@ video_output_features = [
} , {
'name': '--xv',
'desc': 'Xv video output',
- 'deps': [ 'x11' ],
+ 'deps': 'x11',
'func': check_pkg_config('xv'),
} , {
'name': '--gl-cocoa',
'desc': 'OpenGL Cocoa Backend',
- 'deps': [ 'cocoa' ],
+ 'deps': 'cocoa',
'groups': [ 'gl' ],
'func': check_statement('IOSurface/IOSurface.h',
'IOSurfaceRef surface;',
@@ -604,7 +605,7 @@ video_output_features = [
} , {
'name': '--gl-x11',
'desc': 'OpenGL X11 Backend',
- 'deps': [ 'x11' ],
+ 'deps': 'x11',
'groups': [ 'gl' ],
'func': check_libs(['GL', 'GL Xdamage'],
check_cc(fragment=load_fragment('gl_x11.c'),
@@ -612,33 +613,33 @@ video_output_features = [
} , {
'name': '--egl-x11',
'desc': 'OpenGL X11 EGL Backend',
- 'deps': [ 'x11' ],
+ 'deps': 'x11',
'groups': [ 'gl' ],
'func': check_pkg_config('egl'),
} , {
'name': '--egl-drm',
'desc': 'OpenGL DRM EGL Backend',
- 'deps': [ 'drm', 'gbm' ],
+ 'deps': 'drm && gbm',
'groups': [ 'gl' ],
'func': check_pkg_config('egl'),
} , {
'name': '--gl-wayland',
'desc': 'OpenGL Wayland Backend',
- 'deps': [ 'wayland' ],
+ 'deps': 'wayland',
'groups': [ 'gl' ],
'func': check_pkg_config('wayland-egl', '>= 9.0.0',
'egl', '>= 9.0.0')
} , {
'name': '--gl-win32',
'desc': 'OpenGL Win32 Backend',
- 'deps': [ 'win32-desktop' ],
+ 'deps': 'win32-desktop',
'groups': [ 'gl' ],
'func': check_statement('windows.h', 'wglCreateContext(0)',
lib='opengl32')
} , {
'name': '--gl-dxinterop',
'desc': 'OpenGL/DirectX Interop Backend',
- 'deps': [ 'gl-win32' ],
+ 'deps': 'gl-win32',
'groups': [ 'gl' ],
'func': compose_checks(
check_statement(['GL/gl.h', 'GL/wglext.h'], 'int i = WGL_ACCESS_WRITE_DISCARD_NV'),
@@ -646,14 +647,14 @@ video_output_features = [
} , {
'name': '--egl-angle',
'desc': 'OpenGL ANGLE headers',
- 'deps_any': [ 'os-win32', 'os-cygwin' ],
+ 'deps': 'os-win32 || os-cygwin',
'groups': [ 'gl' ],
'func': check_statement(['EGL/egl.h', 'EGL/eglext.h'],
'int x = EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE')
} , {
'name': '--egl-angle-lib',
'desc': 'OpenGL Win32 ANGLE Library',
- 'deps': [ 'egl-angle' ],
+ 'deps': 'egl-angle',
'groups': [ 'gl' ],
'func': check_statement(['EGL/egl.h'],
'eglCreateWindowSurface(0, 0, 0, 0)',
@@ -664,58 +665,58 @@ video_output_features = [
}, {
'name': '--egl-angle-win32',
'desc': 'OpenGL Win32 ANGLE Backend',
- 'deps': [ 'egl-angle', 'win32-desktop' ],
+ 'deps': 'egl-angle && win32-desktop',
'groups': [ 'gl' ],
'func': check_true,
} , {
'name': '--vdpau',
'desc': 'VDPAU acceleration',
- 'deps': [ 'x11' ],
+ 'deps': 'x11',
'func': check_pkg_config('vdpau', '>= 0.2'),
} , {
'name': '--vdpau-gl-x11',
'desc': 'VDPAU with OpenGL/X11',
- 'deps': [ 'vdpau', 'gl-x11' ],
+ 'deps': 'vdpau && gl-x11',
'func': check_true,
}, {
'name': '--vaapi',
'desc': 'VAAPI acceleration',
- 'deps': [ 'libdl' ],
- 'deps_any': [ 'x11', 'wayland', 'egl-drm' ],
+ 'deps': 'libdl && (x11 || wayland || egl-drm)',
'func': check_pkg_config('libva', '>= 0.36.0'),
}, {
'name': '--vaapi-x11',
'desc': 'VAAPI (X11 support)',
- 'deps': [ 'vaapi', 'x11' ],
+ 'deps': 'vaapi && x11',
'func': check_pkg_config('libva-x11', '>= 0.36.0'),
}, {
'name': '--vaapi-wayland',
'desc': 'VAAPI (Wayland support)',
- 'deps': [ 'vaapi', 'gl-wayland' ],
+ 'deps': 'vaapi && gl-wayland',
'func': check_pkg_config('libva-wayland', '>= 0.36.0'),
}, {
'name': '--vaapi-drm',
'desc': 'VAAPI (DRM/EGL support)',
- 'deps': [ 'vaapi', 'egl-drm' ],
+ 'deps': 'vaapi && egl-drm',
'func': check_pkg_config('libva-drm', '>= 0.36.0'),
}, {
'name': '--vaapi-glx',
'desc': 'VAAPI GLX',
- 'deps': [ 'vaapi-x11', 'gl-x11' ],
+ 'deps': 'gpl && vaapi-x11 && gl-x11',
'func': check_true,
}, {
'name': '--vaapi-x-egl',
'desc': 'VAAPI EGL on X11',
- 'deps': [ 'vaapi-x11', 'egl-x11' ],
+ 'deps': 'vaapi-x11 && egl-x11',
'func': check_true,
}, {
'name': 'vaapi-egl',
'desc': 'VAAPI EGL',
- 'deps_any': [ 'vaapi-x-egl', 'vaapi-wayland' ],
+ 'deps': 'vaapi-x-egl || vaapi-wayland',
'func': check_true,
}, {
'name': '--caca',
'desc': 'CACA',
+ 'deps': 'gpl',
'func': check_pkg_config('caca', '>= 0.99.beta18'),
}, {
'name': '--jpeg',
@@ -725,12 +726,36 @@ video_output_features = [
}, {
'name': '--direct3d',
'desc': 'Direct3D support',
- 'deps': [ 'win32-desktop' ],
+ 'deps': 'win32-desktop && gpl',
'func': check_cc(header_name='d3d9.h'),
}, {
- 'name': '--android',
- 'desc': 'Android support',
- 'func': check_statement('android/api-level.h', '(void)__ANDROID__'), # arbitrary android-specific header
+ 'name': 'shaderc-shared',
+ 'desc': 'libshaderc SPIR-V compiler (shared library)',
+ 'deps': '!static-build',
+ 'groups': ['shaderc'],
+ 'func': check_cc(header_name='shaderc/shaderc.h', lib='shaderc_shared'),
+ }, {
+ 'name': 'shaderc-static',
+ 'desc': 'libshaderc SPIR-V compiler (static library)',
+ 'deps': '!shaderc-shared',
+ 'groups': ['shaderc'],
+ 'func': check_cc(header_name='shaderc/shaderc.h',
+ lib=['shaderc_combined', 'glslang', 'SPIRV-Tools',
+ 'SPIRV-Tools-opt', 'stdc++']),
+ }, {
+ 'name': '--shaderc',
+ 'desc': 'libshaderc SPIR-V compiler',
+ 'deps': 'shaderc-shared || shaderc-static',
+ 'func': check_true,
+ }, {
+ 'name': '--crossc',
+ 'desc': 'libcrossc SPIR-V translator',
+ 'func': check_pkg_config('crossc'),
+ }, {
+ 'name': '--d3d11',
+ 'desc': 'Direct3D 11 video output',
+ 'deps': 'win32-desktop && shaderc && crossc',
+ 'func': check_cc(header_name=['d3d11_1.h', 'dxgi1_2.h']),
}, {
# We need MMAL/bcm_host/dispmanx APIs. Also, most RPI distros require
# every project to hardcode the paths to the include directories. Also,
@@ -739,10 +764,10 @@ video_output_features = [
'name': '--rpi',
'desc': 'Raspberry Pi support',
'func': compose_checks(
- check_cc(cflags="-isystem/opt/vc/include/ "+
- "-isystem/opt/vc/include/interface/vcos/pthreads " +
- "-isystem/opt/vc/include/interface/vmcs_host/linux " +
- "-fgnu89-inline",
+ check_cc(cflags=["-isystem/opt/vc/include",
+ "-isystem/opt/vc/include/interface/vcos/pthreads",
+ "-isystem/opt/vc/include/interface/vmcs_host/linux",
+ "-fgnu89-inline"],
linkflags="-L/opt/vc/lib",
header_name="bcm_host.h",
lib=['mmal_core', 'mmal_util', 'mmal_vc_client', 'bcm_host']),
@@ -758,12 +783,12 @@ video_output_features = [
} , {
'name': '--plain-gl',
'desc': 'OpenGL without platform-specific code (e.g. for libmpv)',
- 'deps_any': [ 'libmpv-shared', 'libmpv-static' ],
+ 'deps': 'libmpv-shared || libmpv-static',
'func': check_true,
}, {
'name': '--mali-fbdev',
'desc': 'MALI via Linux fbdev',
- 'deps': ['libdl'],
+ 'deps': 'libdl',
'func': compose_checks(
check_cc(lib="EGL"),
check_statement('EGL/fbdev_window.h', 'struct fbdev_window test'),
@@ -771,116 +796,62 @@ video_output_features = [
),
}, {
'name': '--gl',
- 'desc': 'OpenGL video outputs',
- 'deps_any': [ 'gl-cocoa', 'gl-x11', 'egl-x11', 'egl-drm',
- 'gl-win32', 'gl-wayland', 'rpi', 'mali-fbdev',
- 'plain-gl' ],
+ 'desc': 'OpenGL context support',
+ 'deps': 'gl-cocoa || gl-x11 || egl-x11 || egl-drm || '
+ + 'gl-win32 || gl-wayland || rpi || mali-fbdev || '
+ + 'plain-gl',
'func': check_true,
'req': True,
'fmsg': "No OpenGL video output found or enabled. " +
"Aborting. If you really mean to compile without OpenGL " +
- "video outputs use --disable-gl."
+ "video outputs use --disable-gl.",
+ }, {
+ 'name': '--vulkan',
+ 'desc': 'Vulkan context support',
+ 'func': check_pkg_config('vulkan'),
}, {
'name': 'egl-helpers',
'desc': 'EGL helper functions',
- 'deps_any': [ 'egl-x11', 'mali-fbdev', 'rpi', 'gl-wayland', 'egl-drm',
- 'egl-angle-win32' ],
+ 'deps': 'egl-x11 || mali-fbdev || rpi || gl-wayland || egl-drm || ' +
+ 'egl-angle-win32 || android',
'func': check_true
}
]
hwaccel_features = [
{
- 'name': '--vaapi-hwaccel',
- 'desc': 'libavcodec VAAPI hwaccel (FFmpeg 3.3 API)',
- 'deps': [ 'vaapi' ],
- 'func': check_statement('libavcodec/version.h',
- 'int x[(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 26, 0) && '
- ' LIBAVCODEC_VERSION_MICRO < 100) ||'
- ' (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 74, 100) && '
- ' LIBAVCODEC_VERSION_MICRO >= 100)'
- ' ? 1 : -1]',
- use='libav'),
- }, {
- 'name': '--videotoolbox-hwaccel-new',
- 'desc': 'libavcodec videotoolbox hwaccel (new API)',
- 'deps_any': [ 'gl-cocoa', 'ios-gl' ],
- 'func': check_statement('libavcodec/version.h',
- 'int x[(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 96, 100) && '
- ' LIBAVCODEC_VERSION_MICRO >= 100)'
- ' ? 1 : -1]',
- use='libav'),
- }, {
- 'name': '--videotoolbox-hwaccel-old',
- 'desc': 'libavcodec videotoolbox hwaccel (old API)',
- 'deps_any': [ 'gl-cocoa', 'ios-gl' ],
- 'deps_neg': [ 'videotoolbox-hwaccel-new' ],
- 'func': compose_checks(
- check_headers('VideoToolbox/VideoToolbox.h'),
- check_statement('libavcodec/videotoolbox.h',
- 'av_videotoolbox_alloc_context()',
- use='libav')),
- }, {
'name': 'videotoolbox-hwaccel',
'desc': 'libavcodec videotoolbox hwaccel',
- 'deps_any': [ 'videotoolbox-hwaccel-new', 'videotoolbox-hwaccel-old' ],
+ 'deps': 'gl-cocoa || ios-gl',
'func': check_true,
}, {
'name': '--videotoolbox-gl',
'desc': 'Videotoolbox with OpenGL',
- 'deps': [ 'gl-cocoa', 'videotoolbox-hwaccel' ],
+ 'deps': 'gl-cocoa && videotoolbox-hwaccel',
'func': check_true
}, {
- 'name': '--vdpau-hwaccel',
- 'desc': 'libavcodec VDPAU hwaccel (FFmpeg 3.3 API)',
- 'deps': [ 'vdpau' ],
- 'func': check_statement('libavcodec/version.h',
- 'int x[(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 1) && '
- ' LIBAVCODEC_VERSION_MICRO < 100) ||'
- ' (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 85, 101) && '
- ' LIBAVCODEC_VERSION_MICRO >= 100)'
- ' ? 1 : -1]',
- use='libav'),
- }, {
# (conflated with ANGLE for easier deps)
'name': '--d3d-hwaccel',
'desc': 'D3D11VA hwaccel (plus ANGLE)',
- 'deps': [ 'os-win32', 'egl-angle' ],
+ 'deps': 'os-win32 && egl-angle',
'func': check_true,
}, {
- 'name': '--d3d-hwaccel-new',
- 'desc': 'D3D11VA hwaccel (new API)',
- 'func': check_statement('libavcodec/version.h',
- 'int x[(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 4, 0) && '
- ' LIBAVCODEC_VERSION_MICRO < 100) ||'
- ' (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 100, 100) && '
- ' LIBAVCODEC_VERSION_MICRO >= 100)'
- ' ? 1 : -1]',
- use='libav'),
- 'deps': [ 'd3d-hwaccel' ],
- }, {
'name': '--d3d9-hwaccel',
'desc': 'DXVA2 hwaccel (plus ANGLE)',
- 'deps': [ 'd3d-hwaccel', 'egl-angle-win32' ],
+ 'deps': 'd3d-hwaccel && egl-angle-win32',
'func': check_true,
}, {
'name': '--gl-dxinterop-d3d9',
'desc': 'OpenGL/DirectX Interop Backend DXVA2 interop',
- 'deps': [ 'gl-dxinterop', 'd3d9-hwaccel' ],
+ 'deps': 'gl-dxinterop && d3d9-hwaccel',
'groups': [ 'gl' ],
'func': check_true,
}, {
'name': '--cuda-hwaccel',
'desc': 'CUDA hwaccel',
- 'deps': [ 'gl' ],
+ 'deps': 'gl',
'func': check_cc(fragment=load_fragment('cuda.c'),
- use='libav'),
- }, {
- 'name': 'sse4-intrinsics',
- 'desc': 'GCC SSE4 intrinsics for GPU memcpy',
- 'deps_any': [ 'd3d-hwaccel' ],
- 'deps_neg': [ 'd3d-hwaccel-new' ],
- 'func': check_cc(fragment=load_fragment('sse.c')),
+ use='libavcodec'),
}
]
@@ -888,38 +859,38 @@ radio_and_tv_features = [
{
'name': '--tv',
'desc': 'TV interface',
+ 'deps': 'gpl',
'func': check_true,
'default': 'disable',
}, {
'name': 'sys_videoio_h',
'desc': 'videoio.h',
'func': check_cc(header_name=['sys/time.h', 'sys/videoio.h']),
- 'deps': [ 'tv' ],
+ 'deps': 'tv',
}, {
'name': 'videodev',
'desc': 'videodev2.h',
'func': check_cc(header_name=['sys/time.h', 'linux/videodev2.h']),
- 'deps': [ 'tv' ],
- 'deps_neg': [ 'sys_videoio_h' ],
+ 'deps': 'tv && !sys_videoio_h',
}, {
'name': '--tv-v4l2',
'desc': 'Video4Linux2 TV interface',
- 'deps': [ 'tv' ],
- 'deps_any': [ 'sys_videoio_h', 'videodev' ],
+ 'deps': 'tv && (sys_videoio_h || videodev)',
'func': check_true,
}, {
'name': '--libv4l2',
'desc': 'libv4l2 support',
'func': check_pkg_config('libv4l2'),
- 'deps': [ 'tv-v4l2' ],
+ 'deps': 'tv-v4l2',
}, {
'name': '--audio-input',
'desc': 'audio input support',
- 'deps_any': [ 'tv-v4l2' ],
+ 'deps': 'tv-v4l2',
'func': check_true
} , {
'name': '--dvbin',
'desc': 'DVB input module',
+ 'deps': 'gpl',
'func': check_true,
'default': 'disable',
}
@@ -929,17 +900,17 @@ standalone_features = [
{
'name': 'win32-executable',
'desc': 'w32 executable',
- 'deps_any': [ 'os-win32', 'os-cygwin'],
+ 'deps': 'os-win32 || !(!(os-cygwin))',
'func': check_ctx_vars('WINDRES')
}, {
'name': '--apple-remote',
'desc': 'Apple Remote support',
- 'deps': [ 'cocoa' ],
+ 'deps': 'cocoa',
'func': check_true
}, {
'name': '--macos-touchbar',
'desc': 'macOS Touch Bar support',
- 'deps': [ 'cocoa' ],
+ 'deps': 'cocoa',
'func': check_cc(
fragment=load_fragment('touchbar.m'),
framework_name=['AppKit'],
diff --git a/wscript_build.py b/wscript_build.py
index 3c5c00d..bbe3679 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -4,7 +4,7 @@ import os
def _add_rst_manual_dependencies(ctx):
manpage_sources_basenames = """
options.rst ao.rst vo.rst af.rst vf.rst encode.rst
- input.rst osc.rst lua.rst ipc.rst changes.rst""".split()
+ input.rst osc.rst stats.rst lua.rst ipc.rst changes.rst""".split()
manpage_sources = ['DOCS/man/'+x for x in manpage_sources_basenames]
@@ -100,7 +100,7 @@ def build(ctx):
)
lua_files = ["defaults.lua", "assdraw.lua", "options.lua", "osc.lua",
- "ytdl_hook.lua"]
+ "ytdl_hook.lua", "stats.lua"]
for fn in lua_files:
fn = "player/lua/" + fn
@@ -116,6 +116,26 @@ def build(ctx):
target = "player/javascript/defaults.js.inc",
)
+ if ctx.dependency_satisfied('wayland'):
+ ctx.wayland_protocol_code(proto_dir = ctx.env.WL_PROTO_DIR,
+ protocol = "unstable/xdg-shell/xdg-shell-unstable-v6",
+ target = "video/out/wayland/xdg-shell-v6.c")
+ ctx.wayland_protocol_header(proto_dir = ctx.env.WL_PROTO_DIR,
+ protocol = "unstable/xdg-shell/xdg-shell-unstable-v6",
+ target = "video/out/wayland/xdg-shell-v6.h")
+ ctx.wayland_protocol_code(proto_dir = ctx.env.WL_PROTO_DIR,
+ protocol = "unstable/idle-inhibit/idle-inhibit-unstable-v1",
+ target = "video/out/wayland/idle-inhibit-v1.c")
+ ctx.wayland_protocol_header(proto_dir = ctx.env.WL_PROTO_DIR,
+ protocol = "unstable/idle-inhibit/idle-inhibit-unstable-v1",
+ target = "video/out/wayland/idle-inhibit-v1.h")
+ ctx.wayland_protocol_code(proto_dir = "../video/out/wayland",
+ protocol = "server-decoration",
+ target = "video/out/wayland/srv-decor.c")
+ ctx.wayland_protocol_header(proto_dir = "../video/out/wayland",
+ protocol = "server-decoration",
+ target = "video/out/wayland/srv-decor.h")
+
ctx(features = "ebml_header", target = "ebml_types.h")
ctx(features = "ebml_definitions", target = "ebml_defs.c")
@@ -152,7 +172,8 @@ def build(ctx):
sources = [
## Audio
- ( "audio/audio.c" ),
+ ( "audio/aconverter.c" ),
+ ( "audio/audio.c", "libaf" ),
( "audio/audio_buffer.c" ),
( "audio/chmap.c" ),
( "audio/chmap_sel.c" ),
@@ -162,18 +183,14 @@ def build(ctx):
( "audio/decode/ad_lavc.c" ),
( "audio/decode/ad_spdif.c" ),
( "audio/decode/dec_audio.c" ),
- ( "audio/filter/af.c" ),
- ( "audio/filter/af_channels.c" ),
- ( "audio/filter/af_equalizer.c" ),
- ( "audio/filter/af_format.c" ),
- ( "audio/filter/af_lavcac3enc.c" ),
- ( "audio/filter/af_lavfi.c" ),
- ( "audio/filter/af_lavrresample.c" ),
- ( "audio/filter/af_pan.c" ),
+ ( "audio/filter/af.c", "libaf" ),
+ ( "audio/filter/af_format.c", "libaf" ),
+ ( "audio/filter/af_lavcac3enc.c", "libaf" ),
+ ( "audio/filter/af_lavfi.c", "libaf" ),
+ ( "audio/filter/af_lavrresample.c", "libaf" ),
( "audio/filter/af_rubberband.c", "rubberband" ),
- ( "audio/filter/af_scaletempo.c" ),
- ( "audio/filter/af_volume.c" ),
- ( "audio/filter/tools.c" ),
+ ( "audio/filter/af_scaletempo.c", "libaf" ),
+ ( "audio/filter/tools.c", "libaf" ),
( "audio/out/ao.c" ),
( "audio/out/ao_alsa.c", "alsa" ),
( "audio/out/ao_audiounit.m", "audiounit" ),
@@ -285,9 +302,9 @@ def build(ctx):
( "player/video.c" ),
## Streams
- ( "stream/ai_alsa1x.c", "alsa" ),
- ( "stream/ai_oss.c", "oss-audio" ),
- ( "stream/ai_sndio.c", "sndio" ),
+ ( "stream/ai_alsa1x.c", "alsa && audio-input" ),
+ ( "stream/ai_oss.c", "oss-audio && audio-input" ),
+ ( "stream/ai_sndio.c", "sndio && audio-input" ),
( "stream/audio_in.c", "audio-input" ),
( "stream/cache.c" ),
( "stream/cache_file.c" ),
@@ -333,8 +350,8 @@ def build(ctx):
## Video
( "video/csputils.c" ),
+ ( "video/d3d.c", "d3d-hwaccel" ),
( "video/fmt-conversion.c" ),
- ( "video/gpu_memcpy.c", "sse4-intrinsics" ),
( "video/image_loader.c" ),
( "video/image_writer.c" ),
( "video/img_format.c" ),
@@ -345,36 +362,18 @@ def build(ctx):
( "video/vaapi.c", "vaapi" ),
( "video/vdpau.c", "vdpau" ),
( "video/vdpau_mixer.c", "vdpau" ),
- ( "video/vt.c", "videotoolbox-hwaccel" ),
- ( "video/decode/d3d.c", "d3d-hwaccel" ),
( "video/decode/dec_video.c"),
- ( "video/decode/hw_cuda.c", "cuda-hwaccel" ),
- ( "video/decode/hw_dxva2.c", "d3d9-hwaccel" ),
- ( "video/decode/hw_d3d11va.c", "d3d-hwaccel" ),
- ( "video/decode/hw_videotoolbox.c", "videotoolbox-hwaccel" ),
( "video/decode/vd_lavc.c" ),
( "video/filter/refqueue.c" ),
( "video/filter/vf.c" ),
- ( "video/filter/vf_buffer.c" ),
- ( "video/filter/vf_crop.c" ),
+ ( "video/filter/vf_convert.c" ),
( "video/filter/vf_d3d11vpp.c", "d3d-hwaccel" ),
- ( "video/filter/vf_dsize.c" ),
- ( "video/filter/vf_expand.c" ),
- ( "video/filter/vf_flip.c" ),
( "video/filter/vf_format.c" ),
- ( "video/filter/vf_gradfun.c" ),
( "video/filter/vf_lavfi.c" ),
- ( "video/filter/vf_mirror.c" ),
- ( "video/filter/vf_noformat.c" ),
- ( "video/filter/vf_pullup.c" ),
- ( "video/filter/vf_rotate.c" ),
- ( "video/filter/vf_scale.c" ),
- ( "video/filter/vf_stereo3d.c" ),
( "video/filter/vf_sub.c" ),
( "video/filter/vf_vapoursynth.c", "vapoursynth-core" ),
( "video/filter/vf_vavpp.c", "vaapi" ),
( "video/filter/vf_vdpaupp.c", "vdpau" ),
- ( "video/filter/vf_yadif.c" ),
( "video/out/aspect.c" ),
( "video/out/bitmap_packer.c" ),
( "video/out/cocoa/video_view.m", "cocoa" ),
@@ -383,26 +382,42 @@ def build(ctx):
( "video/out/cocoa_common.m", "cocoa" ),
( "video/out/dither.c" ),
( "video/out/filter_kernels.c" ),
+ ( "video/out/d3d11/context.c", "d3d11" ),
+ ( "video/out/d3d11/hwdec_d3d11va.c", "d3d11 && d3d-hwaccel" ),
+ ( "video/out/d3d11/ra_d3d11.c", "d3d11" ),
( "video/out/opengl/angle_dynamic.c", "egl-angle" ),
+ ( "video/out/gpu/context.c" ),
+ ( "video/out/gpu/d3d11_helpers.c", "d3d11 || egl-angle-win32" ),
+ ( "video/out/gpu/hwdec.c" ),
+ ( "video/out/gpu/lcms.c" ),
+ ( "video/out/gpu/osd.c" ),
+ ( "video/out/gpu/ra.c" ),
+ ( "video/out/gpu/spirv.c" ),
+ ( "video/out/gpu/spirv_shaderc.c", "shaderc" ),
+ ( "video/out/gpu/shader_cache.c" ),
+ ( "video/out/gpu/user_shaders.c" ),
+ ( "video/out/gpu/utils.c" ),
+ ( "video/out/gpu/video.c" ),
+ ( "video/out/gpu/video_shaders.c" ),
( "video/out/opengl/common.c", "gl" ),
+ ( "video/out/opengl/formats.c", "gl" ),
+ ( "video/out/opengl/utils.c", "gl" ),
+ ( "video/out/opengl/ra_gl.c", "gl" ),
( "video/out/opengl/context.c", "gl" ),
( "video/out/opengl/context_angle.c", "egl-angle-win32" ),
( "video/out/opengl/context_cocoa.c", "gl-cocoa" ),
( "video/out/opengl/context_drm_egl.c", "egl-drm" ),
( "video/out/opengl/context_dxinterop.c","gl-dxinterop" ),
( "video/out/opengl/context_mali_fbdev.c","mali-fbdev" ),
+ ( "video/out/opengl/context_android.c", "android" ),
( "video/out/opengl/context_rpi.c", "rpi" ),
( "video/out/opengl/context_vdpau.c", "vdpau-gl-x11" ),
( "video/out/opengl/context_wayland.c", "gl-wayland" ),
- ( "video/out/opengl/context_w32.c", "gl-win32" ),
- ( "video/out/opengl/context_x11.c", "gl-x11" ),
+ ( "video/out/opengl/context_win.c", "gl-win32" ),
+ ( "video/out/opengl/context_glx.c", "gl-x11" ),
( "video/out/opengl/context_x11egl.c", "egl-x11" ),
( "video/out/opengl/cuda_dynamic.c", "cuda-hwaccel" ),
- ( "video/out/opengl/d3d11_helpers.c", "egl-angle-win32" ),
( "video/out/opengl/egl_helpers.c", "egl-helpers" ),
- ( "video/out/opengl/formats.c", "gl" ),
- ( "video/out/opengl/gl_utils.c", "gl" ),
- ( "video/out/opengl/hwdec.c", "gl" ),
( "video/out/opengl/hwdec_cuda.c", "cuda-hwaccel" ),
( "video/out/opengl/hwdec_d3d11egl.c", "d3d-hwaccel" ),
( "video/out/opengl/hwdec_d3d11eglrgb.c","d3d-hwaccel" ),
@@ -410,20 +425,12 @@ def build(ctx):
( "video/out/opengl/hwdec_dxva2egl.c", "d3d9-hwaccel" ),
( "video/out/opengl/hwdec_osx.c", "videotoolbox-gl" ),
( "video/out/opengl/hwdec_ios.m", "ios-gl" ),
+ ( "video/out/opengl/hwdec_drmprime_drm.c","drmprime && drm" ),
( "video/out/opengl/hwdec_rpi.c", "rpi" ),
( "video/out/opengl/hwdec_vaegl.c", "vaapi-egl" ),
- ( "video/out/opengl/hwdec_vaglx.c", "vaapi-glx" ),
( "video/out/opengl/hwdec_vdpau.c", "vdpau-gl-x11" ),
- ( "video/out/opengl/lcms.c", "gl" ),
- ( "video/out/opengl/osd.c", "gl" ),
- ( "video/out/opengl/ra.c", "gl" ),
- ( "video/out/opengl/ra_gl.c", "gl" ),
- ( "video/out/opengl/shader_cache.c", "gl" ),
- ( "video/out/opengl/user_shaders.c", "gl" ),
- ( "video/out/opengl/utils.c", "gl" ),
- ( "video/out/opengl/video.c", "gl" ),
- ( "video/out/opengl/video_shaders.c", "gl" ),
( "video/out/vo.c" ),
+ ( "video/out/vo_mediacodec_embed.c", "android" ),
( "video/out/vo_caca.c", "caca" ),
( "video/out/vo_drm.c", "drm" ),
( "video/out/vo_direct3d.c", "direct3d" ),
@@ -431,25 +438,35 @@ def build(ctx):
( "video/out/vo_lavc.c", "encoding" ),
( "video/out/vo_rpi.c", "rpi" ),
( "video/out/vo_null.c" ),
- ( "video/out/vo_opengl.c", "gl" ),
+ ( "video/out/vo_gpu.c" ),
( "video/out/vo_opengl_cb.c", "gl" ),
( "video/out/vo_sdl.c", "sdl2" ),
( "video/out/vo_tct.c" ),
- ( "video/out/vo_vaapi.c", "vaapi-x11" ),
+ ( "video/out/vo_vaapi.c", "vaapi-x11 && gpl" ),
( "video/out/vo_vdpau.c", "vdpau" ),
- ( "video/out/vo_wayland.c", "wayland" ),
( "video/out/vo_x11.c" , "x11" ),
( "video/out/vo_xv.c", "xv" ),
( "video/out/w32_common.c", "win32-desktop" ),
( "video/out/win32/displayconfig.c", "win32-desktop" ),
( "video/out/win32/droptarget.c", "win32-desktop" ),
- ( "video/out/win32/exclusive_hack.c", "gl-win32" ),
+ ( "video/out/vulkan/utils.c", "vulkan" ),
+ ( "video/out/vulkan/malloc.c", "vulkan" ),
+ ( "video/out/vulkan/formats.c", "vulkan" ),
+ ( "video/out/vulkan/ra_vk.c", "vulkan" ),
+ ( "video/out/vulkan/context.c", "vulkan" ),
+ ( "video/out/vulkan/context_xlib.c", "vulkan && x11" ),
+ ( "video/out/vulkan/context_wayland.c", "vulkan && wayland" ),
+ ( "video/out/vulkan/context_win.c", "vulkan && win32-desktop" ),
+ ( "video/out/vulkan/spirv_nvidia.c", "vulkan" ),
( "video/out/wayland_common.c", "wayland" ),
- ( "video/out/wayland/buffer.c", "wayland" ),
- ( "video/out/wayland/memfile.c", "wayland" ),
+ ( "video/out/wayland/xdg-shell-v6.c", "wayland" ),
+ ( "video/out/wayland/idle-inhibit-v1.c", "wayland" ),
+ ( "video/out/wayland/srv-decor.c", "wayland" ),
( "video/out/win_state.c"),
( "video/out/x11_common.c", "x11" ),
+ ( "video/out/drm_atomic.c", "drm" ),
( "video/out/drm_common.c", "drm" ),
+ ( "video/out/drm_prime.c", "drm && drmprime" ),
## osdep
( getch2_c ),
@@ -457,6 +474,7 @@ def build(ctx):
( "osdep/timer.c" ),
( timer_c ),
( "osdep/threads.c" ),
+ ( "osdep/polldev.c", "posix" ),
( "osdep/ar/HIDRemote.m", "apple-remote" ),
( "osdep/macosx_application.m", "cocoa" ),
@@ -477,6 +495,7 @@ def build(ctx):
( "osdep/windows_utils.c", "os-cygwin" ),
( "osdep/mpv.rc", "win32-executable" ),
( "osdep/win32/pthread.c", "win32-internal-pthreads"),
+ ( "osdep/android/posix-spawn.c", "android"),
( "osdep/android/strnlen.c", "android"),
## tree_allocator