From 5a3b54c78ce63d899f76dbb3db72e4894b40bd53 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 03:13:14 +0200 Subject: Merging upstream version 0.38.0. Signed-off-by: Daniel Baumann --- video/out/cocoa_cb_common.swift | 59 +- video/out/d3d11/context.c | 75 ++- video/out/d3d11/context.h | 4 + video/out/drm_common.c | 9 +- video/out/drm_common.h | 11 +- video/out/filter_kernels.c | 4 +- video/out/gpu/context.c | 11 +- video/out/gpu/context.h | 2 +- video/out/gpu/d3d11_helpers.c | 129 +++-- video/out/gpu/d3d11_helpers.h | 19 +- video/out/gpu/error_diffusion.c | 3 +- video/out/gpu/hwdec.c | 4 - video/out/gpu/hwdec.h | 8 +- video/out/gpu/lcms.c | 62 ++- video/out/gpu/lcms.h | 6 +- video/out/gpu/libmpv_gpu.c | 2 +- video/out/gpu/osd.c | 4 +- video/out/gpu/osd.h | 2 +- video/out/gpu/ra.h | 4 +- video/out/gpu/spirv.c | 1 + video/out/gpu/user_shaders.c | 4 +- video/out/gpu/user_shaders.h | 2 +- video/out/gpu/utils.c | 6 +- video/out/gpu/utils.h | 2 +- video/out/gpu/video.c | 358 ++++++------ video/out/gpu/video.h | 17 +- video/out/gpu/video_shaders.c | 128 ++--- video/out/gpu/video_shaders.h | 9 +- video/out/gpu_next/context.c | 25 +- video/out/gpu_next/context.h | 4 +- video/out/hwdec/dmabuf_interop.h | 2 +- video/out/hwdec/dmabuf_interop_gl.c | 176 ++++-- video/out/hwdec/dmabuf_interop_pl.c | 2 +- video/out/hwdec/hwdec_aimagereader.c | 12 +- video/out/hwdec/hwdec_cuda.c | 2 +- video/out/hwdec/hwdec_drmprime.c | 27 +- video/out/hwdec/hwdec_vaapi.c | 10 +- video/out/hwdec/hwdec_vt.c | 2 +- video/out/mac/common.swift | 228 ++++---- video/out/mac/gl_layer.swift | 32 +- video/out/mac/metal_layer.swift | 12 + video/out/mac/title_bar.swift | 16 +- video/out/mac/view.swift | 152 ++--- video/out/mac/window.swift | 83 ++- video/out/mac_common.swift | 75 ++- video/out/opengl/common.c | 2 +- video/out/opengl/context_drm_egl.c | 4 + video/out/opengl/context_glx.c | 2 +- video/out/opengl/context_rpi.c | 327 ----------- video/out/opengl/context_wayland.c | 4 +- video/out/opengl/context_win.c | 22 +- video/out/opengl/egl_helpers.c | 10 +- video/out/opengl/formats.c | 2 +- video/out/opengl/hwdec_rpi.c | 384 ------------- video/out/placebo/ra_pl.c | 2 +- video/out/placebo/utils.c | 206 +------ video/out/placebo/utils.h | 13 - video/out/vo.c | 127 +++-- video/out/vo.h | 28 +- video/out/vo_direct3d.c | 13 + video/out/vo_dmabuf_wayland.c | 32 +- video/out/vo_drm.c | 85 ++- video/out/vo_gpu.c | 30 +- video/out/vo_gpu_next.c | 511 +++++++++++------ video/out/vo_image.c | 2 +- video/out/vo_kitty.c | 5 +- video/out/vo_lavc.c | 6 +- video/out/vo_libmpv.c | 14 +- video/out/vo_rpi.c | 938 ------------------------------- video/out/vo_sdl.c | 7 + video/out/vo_tct.c | 154 +++--- video/out/vo_vaapi.c | 2 +- video/out/vo_vdpau.c | 32 +- video/out/vo_wlshm.c | 16 +- video/out/vo_x11.c | 4 + video/out/vo_xv.c | 14 +- video/out/vulkan/common.h | 1 - video/out/vulkan/context.c | 49 +- video/out/vulkan/context_display.c | 44 +- video/out/vulkan/context_mac.m | 22 +- video/out/vulkan/context_wayland.c | 2 +- video/out/vulkan/context_win.c | 21 +- video/out/w32_common.c | 457 +++++++++++---- video/out/w32_common.h | 1 + video/out/wayland_common.c | 1008 ++++++++++++++++++++++------------ video/out/wayland_common.h | 31 +- video/out/win32/droptarget.c | 5 +- video/out/win32/menu.c | 231 ++++++++ video/out/win32/menu.h | 32 ++ video/out/x11_common.c | 203 ++++--- video/out/x11_common.h | 8 +- 91 files changed, 3190 insertions(+), 3728 deletions(-) delete mode 100644 video/out/opengl/context_rpi.c delete mode 100644 video/out/opengl/hwdec_rpi.c delete mode 100644 video/out/vo_rpi.c create mode 100644 video/out/win32/menu.c create mode 100644 video/out/win32/menu.h (limited to 'video/out') diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift index 9c0054a..9f32ed6 100644 --- a/video/out/cocoa_cb_common.swift +++ b/video/out/cocoa_cb_common.swift @@ -17,11 +17,11 @@ import Cocoa -class CocoaCB: Common { +class CocoaCB: Common, EventSubscriber { var libmpv: LibmpvHelper var layer: GLLayer? - @objc var isShuttingDown: Bool = false + var isShuttingDown: Bool = false enum State { case uninitialized @@ -30,22 +30,24 @@ class CocoaCB: Common { } var backendState: State = .uninitialized - - @objc init(_ mpvHandle: OpaquePointer) { - let newlog = mp_log_new(UnsafeMutablePointer(mpvHandle), mp_client_get_log(mpvHandle), "cocoacb") - libmpv = LibmpvHelper(mpvHandle, newlog) - super.init(newlog) + init(_ mpv: OpaquePointer) { + let log = LogHelper(mp_log_new(UnsafeMutablePointer(mpv), mp_client_get_log(mpv), "cocoacb")) + let option = OptionHelper(UnsafeMutablePointer(mpv), mp_client_get_global(mpv)) + libmpv = LibmpvHelper(mpv, log) + super.init(option, log) layer = GLLayer(cocoaCB: self) + AppHub.shared.event?.subscribe(self, event: .init(name: "MPV_EVENT_SHUTDOWN")) } func preinit(_ vo: UnsafeMutablePointer) { - mpv = MPVHelper(vo, log) + self.vo = vo + input = InputHelper(vo.pointee.input_ctx, option) if backendState == .uninitialized { backendState = .needsInit guard let layer = self.layer else { - log.sendError("Something went wrong, no GLLayer was initialized") + log.error("Something went wrong, no GLLayer was initialized") exit(1) } @@ -57,17 +59,19 @@ class CocoaCB: Common { func uninit() { window?.orderOut(nil) window?.close() - mpv = nil } func reconfig(_ vo: UnsafeMutablePointer) { - mpv?.vo = vo + self.vo = vo if backendState == .needsInit { DispatchQueue.main.sync { self.initBackend(vo) } - } else { + } else if option.vo.auto_window_resize { DispatchQueue.main.async { self.updateWindowSize(vo) self.layer?.update(force: true) + if self.option.vo.focus_on == 2 { + NSApp.activate(ignoringOtherApps: true) + } } } } @@ -85,15 +89,12 @@ class CocoaCB: Common { func updateWindowSize(_ vo: UnsafeMutablePointer) { guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else { - log.sendWarning("Couldn't update Window size, no Screen available") + log.warning("Couldn't update Window size, no Screen available") return } let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) - if !(window?.isVisible ?? false) && - !(window?.isMiniaturized ?? false) && - !NSApp.isHidden - { + if !(window?.isVisible ?? false) && !(window?.isMiniaturized ?? false) && !NSApp.isHidden { window?.makeKeyAndOrderFront(nil) } layer?.atomicDrawingStart() @@ -116,7 +117,7 @@ class CocoaCB: Common { override func updateICCProfile() { guard let colorSpace = window?.screen?.colorSpace else { - log.sendWarning("Couldn't update ICC Profile, no color space available") + log.warning("Couldn't update ICC Profile, no color space available") return } @@ -169,7 +170,7 @@ class CocoaCB: Common { let ccb = unsafeBitCast(ctx, to: CocoaCB.self) guard let vo = v, let events = e else { - ccb.log.sendWarning("Unexpected nil value in Control Callback") + ccb.log.warning("Unexpected nil value in Control Callback") return VO_FALSE } @@ -198,9 +199,9 @@ class CocoaCB: Common { return super.control(vo, events: events, request: request, data: data) } - func shutdown(_ destroy: Bool = false) { + func shutdown() { isShuttingDown = window?.isAnimating ?? false || - window?.isInFullscreen ?? false && mpv?.opts.native_fs ?? true + window?.isInFullscreen ?? false && option.vo.native_fs if window?.isInFullscreen ?? false && !(window?.isAnimating ?? false) { window?.close() } @@ -209,22 +210,18 @@ class CocoaCB: Common { uninit() uninitCommon() - libmpv.deinitRender() - libmpv.deinitMPV(destroy) + layer?.lockCglContext() + libmpv.uninit() + layer?.unlockCglContext() } func checkShutdown() { if isShuttingDown { - shutdown(true) + shutdown() } } - @objc func processEvent(_ event: UnsafePointer) { - switch event.pointee.event_id { - case MPV_EVENT_SHUTDOWN: - shutdown() - default: - break - } + func handle(event: EventHelper.Event) { + if event.name == String(describing: MPV_EVENT_SHUTDOWN) { shutdown() } } } diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c index 05f04fd..c563b5f 100644 --- a/video/out/d3d11/context.c +++ b/video/out/d3d11/context.c @@ -27,10 +27,6 @@ #include "context.h" #include "ra_d3d11.h" -static int d3d11_validate_adapter(struct mp_log *log, - const struct m_option *opt, - struct bstr name, const char **value); - struct d3d11_opts { int feature_level; int warp; @@ -62,7 +58,7 @@ const struct m_sub_options d3d11_conf = { {"d3d11-flip", OPT_BOOL(flip)}, {"d3d11-sync-interval", OPT_INT(sync_interval), M_RANGE(0, 4)}, {"d3d11-adapter", OPT_STRING_VALIDATE(adapter_name, - d3d11_validate_adapter)}, + mp_dxgi_validate_adapter)}, {"d3d11-output-format", OPT_CHOICE(output_format, {"auto", DXGI_FORMAT_UNKNOWN}, {"rgba8", DXGI_FORMAT_R8G8B8A8_UNORM}, @@ -100,7 +96,7 @@ struct priv { struct ra_tex *backbuffer; ID3D11Device *device; IDXGISwapChain *swapchain; - struct mp_colorspace swapchain_csp; + struct pl_color_space swapchain_csp; int64_t perf_freq; unsigned sync_refresh_count; @@ -109,37 +105,6 @@ struct priv { int64_t last_submit_qpc; }; -static int d3d11_validate_adapter(struct mp_log *log, - const struct m_option *opt, - struct bstr name, const char **value) -{ - struct bstr param = bstr0(*value); - bool help = bstr_equals0(param, "help"); - bool adapter_matched = false; - struct bstr listing = { 0 }; - - if (bstr_equals0(param, "")) { - return 0; - } - - adapter_matched = mp_d3d11_list_or_verify_adapters(log, - help ? bstr0(NULL) : param, - help ? &listing : NULL); - - if (help) { - mp_info(log, "Available D3D11 adapters:\n%.*s", - BSTR_P(listing)); - talloc_free(listing.start); - return M_OPT_EXIT; - } - - if (!adapter_matched) { - mp_err(log, "No adapter matching '%.*s'!\n", BSTR_P(param)); - } - - return adapter_matched ? 0 : M_OPT_INVALID; -} - static struct ra_tex *get_backbuffer(struct ra_ctx *ctx) { struct priv *p = ctx->priv; @@ -189,6 +154,11 @@ static bool d3d11_reconfig(struct ra_ctx *ctx) static int d3d11_color_depth(struct ra_swapchain *sw) { struct priv *p = sw->priv; + + DXGI_OUTPUT_DESC1 desc1; + if (mp_get_dxgi_output_desc(p->swapchain, &desc1)) + return desc1.BitsPerColor; + DXGI_SWAP_CHAIN_DESC desc; HRESULT hr = IDXGISwapChain_GetDesc(p->swapchain, &desc); @@ -514,6 +484,9 @@ static bool d3d11_init(struct ra_ctx *ctx) if (!vo_w32_init(ctx->vo)) goto error; + if (ctx->opts.want_alpha) + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); + UINT usage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; if (ID3D11Device_GetFeatureLevel(p->device) >= D3D_FEATURE_LEVEL_11_0 && p->opts->output_format != DXGI_FORMAT_B8G8R8A8_UNORM) @@ -544,6 +517,11 @@ error: return false; } +static void d3d11_update_render_opts(struct ra_ctx *ctx) +{ + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); +} + IDXGISwapChain *ra_d3d11_ctx_get_swapchain(struct ra_ctx *ra) { if (ra->swapchain->fns != &d3d11_swapchain) @@ -556,11 +534,22 @@ IDXGISwapChain *ra_d3d11_ctx_get_swapchain(struct ra_ctx *ra) return p->swapchain; } +bool ra_d3d11_ctx_prefer_8bit_output_format(struct ra_ctx *ra) +{ + if (ra->swapchain->fns != &d3d11_swapchain) + return false; + + struct priv *p = ra->priv; + + return p->opts->output_format == DXGI_FORMAT_R8G8B8A8_UNORM; +} + const struct ra_ctx_fns ra_ctx_d3d11 = { - .type = "d3d11", - .name = "d3d11", - .reconfig = d3d11_reconfig, - .control = d3d11_control, - .init = d3d11_init, - .uninit = d3d11_uninit, + .type = "d3d11", + .name = "d3d11", + .reconfig = d3d11_reconfig, + .control = d3d11_control, + .update_render_opts = d3d11_update_render_opts, + .init = d3d11_init, + .uninit = d3d11_uninit, }; diff --git a/video/out/d3d11/context.h b/video/out/d3d11/context.h index 8a9ef4c..25488f2 100644 --- a/video/out/d3d11/context.h +++ b/video/out/d3d11/context.h @@ -7,3 +7,7 @@ // Get the underlying D3D11 swap chain from an RA context. The returned swap chain is // refcounted and must be released by the caller. IDXGISwapChain *ra_d3d11_ctx_get_swapchain(struct ra_ctx *ra); + +// Returns true if an 8-bit output format is explicitly requested for +// d3d11-output-format for an RA context. +bool ra_d3d11_ctx_prefer_8bit_output_format(struct ra_ctx *ra); diff --git a/video/out/drm_common.c b/video/out/drm_common.c index da45ca2..e47de7d 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -65,8 +65,7 @@ static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt static int drm_mode_opt_help(struct mp_log *log, const struct m_option *opt, struct bstr name); -static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value); +static OPT_STRING_VALIDATE_FUNC(drm_validate_mode_opt); static void drm_show_available_modes(struct mp_log *log, const drmModeConnector *connector); @@ -96,7 +95,8 @@ const struct m_sub_options drm_conf = { {"xrgb8888", DRM_OPTS_FORMAT_XRGB8888}, {"xrgb2101010", DRM_OPTS_FORMAT_XRGB2101010}, {"xbgr8888", DRM_OPTS_FORMAT_XBGR8888}, - {"xbgr2101010", DRM_OPTS_FORMAT_XBGR2101010})}, + {"xbgr2101010", DRM_OPTS_FORMAT_XBGR2101010}, + {"yuyv", DRM_OPTS_FORMAT_YUYV})}, {"drm-draw-surface-size", OPT_SIZE_BOX(draw_surface_size)}, {"drm-vrr-enabled", OPT_CHOICE(vrr_enabled, {"no", 0}, {"yes", 1}, {"auto", -1})}, @@ -107,6 +107,7 @@ const struct m_sub_options drm_conf = { .drm_atomic = 1, .draw_plane = DRM_OPTS_PRIMARY_PLANE, .drmprime_video_plane = DRM_OPTS_OVERLAY_PLANE, + .drm_format = DRM_OPTS_FORMAT_XRGB8888, }, .size = sizeof(struct drm_opts), }; @@ -387,7 +388,7 @@ bool vo_drm_acquire_crtc(struct vo_drm_state *drm) drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_H", drm->mode.mode.vdisplay); if (drmModeAtomicCommit(drm->fd, request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL)) { - MP_ERR(drm, "Failed to commit ModeSetting atomic request: %s\n", strerror(errno)); + MP_ERR(drm, "Failed to commit ModeSetting atomic request: %s\n", mp_strerror(errno)); goto err; } diff --git a/video/out/drm_common.h b/video/out/drm_common.h index 581151f..9418b3f 100644 --- a/video/out/drm_common.h +++ b/video/out/drm_common.h @@ -23,10 +23,13 @@ #include #include "vo.h" -#define DRM_OPTS_FORMAT_XRGB8888 0 -#define DRM_OPTS_FORMAT_XRGB2101010 1 -#define DRM_OPTS_FORMAT_XBGR8888 2 -#define DRM_OPTS_FORMAT_XBGR2101010 3 +enum { + DRM_OPTS_FORMAT_XRGB8888, + DRM_OPTS_FORMAT_XRGB2101010, + DRM_OPTS_FORMAT_XBGR8888, + DRM_OPTS_FORMAT_XBGR2101010, + DRM_OPTS_FORMAT_YUYV, +}; struct framebuffer { int fd; diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c index 95d99ff..d5df985 100644 --- a/video/out/filter_kernels.c +++ b/video/out/filter_kernels.c @@ -5,10 +5,10 @@ * (https://github.com/glumpy/glumpy/blob/master/glumpy/library/build-spatial-filters.py) * * Also see: - * - http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h + * - https://sourceforge.net/p/agg/svn/HEAD/tree/agg-2.4/include/agg_image_filters.h * - Vapoursynth plugin fmtconv (WTFPL Licensed), which is based on * dither plugin for avisynth from the same author: - * https://github.com/vapoursynth/fmtconv/tree/master/src/fmtc + * https://gitlab.com/EleonoreMizo/fmtconv/-/tree/master/src/fmtc * - Paul Heckbert's "zoom" * - XBMC: ConvolutionKernels.cpp etc. * diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c index 5ce18af..88d4f42 100644 --- a/video/out/gpu/context.c +++ b/video/out/gpu/context.c @@ -41,7 +41,6 @@ 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; /* Vulkan */ @@ -67,9 +66,6 @@ static const struct ra_ctx_fns *contexts[] = { #if HAVE_EGL_ANDROID &ra_ctx_android, #endif -#if HAVE_RPI - &ra_ctx_rpi, -#endif #if HAVE_EGL_ANGLE_WIN32 &ra_ctx_angle, #endif @@ -133,8 +129,7 @@ static int ra_ctx_api_help(struct mp_log *log, const struct m_option *opt, return M_OPT_EXIT; } -static int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(ra_ctx_validate_api) { struct bstr param = bstr0(*value); if (bstr_equals0(param, "auto")) @@ -158,8 +153,7 @@ static int ra_ctx_context_help(struct mp_log *log, const struct m_option *opt, return M_OPT_EXIT; } -static int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(ra_ctx_validate_context) { struct bstr param = bstr0(*value); if (bstr_equals0(param, "auto")) @@ -208,6 +202,7 @@ struct ra_ctx *ra_ctx_create(struct vo *vo, struct ra_ctx_opts opts) MP_VERBOSE(ctx, "Initializing GPU context '%s'\n", ctx->fns->name); if (contexts[i]->init(ctx)) { vo->probing = old_probing; + vo->context_name = ctx->fns->name; return ctx; } diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index 6788e6f..447f40b 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -72,7 +72,7 @@ struct ra_fbo { // Host system's colorspace that it will be interpreting // the frame buffer as. - struct mp_colorspace color_space; + struct pl_color_space color_space; }; struct ra_swapchain_fns { diff --git a/video/out/gpu/d3d11_helpers.c b/video/out/gpu/d3d11_helpers.c index 30d9eae..d45c038 100644 --- a/video/out/gpu/d3d11_helpers.c +++ b/video/out/gpu/d3d11_helpers.c @@ -228,9 +228,9 @@ static const char *d3d11_get_csp_name(DXGI_COLOR_SPACE_TYPE csp) } static bool d3d11_get_mp_csp(DXGI_COLOR_SPACE_TYPE csp, - struct mp_colorspace *mp_csp) + struct pl_color_space *pl_color_system) { - if (!mp_csp) + if (!pl_color_system) return false; // Colorspaces utilizing gamma 2.2 (G22) are set to @@ -243,27 +243,27 @@ static bool d3d11_get_mp_csp(DXGI_COLOR_SPACE_TYPE csp, // regarding not doing conversion from BT.601 to BT.709. switch (csp) { case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709: - *mp_csp = (struct mp_colorspace){ - .gamma = MP_CSP_TRC_AUTO, - .primaries = MP_CSP_PRIM_AUTO, + *pl_color_system = (struct pl_color_space){ + .transfer = PL_COLOR_TRC_UNKNOWN, + .primaries = PL_COLOR_PRIM_UNKNOWN, }; break; case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709: - *mp_csp = (struct mp_colorspace) { - .gamma = MP_CSP_TRC_LINEAR, - .primaries = MP_CSP_PRIM_AUTO, + *pl_color_system = (struct pl_color_space) { + .transfer = PL_COLOR_TRC_LINEAR, + .primaries = PL_COLOR_PRIM_UNKNOWN, }; break; case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: - *mp_csp = (struct mp_colorspace) { - .gamma = MP_CSP_TRC_PQ, - .primaries = MP_CSP_PRIM_BT_2020, + *pl_color_system = (struct pl_color_space) { + .transfer = PL_COLOR_TRC_PQ, + .primaries = PL_COLOR_PRIM_BT_2020, }; break; case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020: - *mp_csp = (struct mp_colorspace) { - .gamma = MP_CSP_TRC_AUTO, - .primaries = MP_CSP_PRIM_BT_2020, + *pl_color_system = (struct pl_color_space) { + .transfer = PL_COLOR_TRC_UNKNOWN, + .primaries = PL_COLOR_PRIM_BT_2020, }; break; default: @@ -287,28 +287,8 @@ static bool query_output_format_and_colorspace(struct mp_log *log, if (!out_fmt || !out_cspace) return false; - HRESULT hr = IDXGISwapChain_GetContainingOutput(swapchain, &output); - if (FAILED(hr)) { - mp_err(log, "Failed to get swap chain's containing output: %s!\n", - mp_HRESULT_to_str(hr)); - goto done; - } - - hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput6, - (void**)&output6); - if (FAILED(hr)) { - // point where systems older than Windows 10 would fail, - // thus utilizing error log level only with windows 10+ - mp_msg(log, IsWindows10OrGreater() ? MSGL_ERR : MSGL_V, - "Failed to create a DXGI 1.6 output interface: %s\n", - mp_HRESULT_to_str(hr)); - goto done; - } - - hr = IDXGIOutput6_GetDesc1(output6, &desc); - if (FAILED(hr)) { - mp_err(log, "Failed to query swap chain's output information: %s\n", - mp_HRESULT_to_str(hr)); + if (!mp_get_dxgi_output_desc(swapchain, &desc)) { + mp_err(log, "Failed to query swap chain's output information\n"); goto done; } @@ -371,9 +351,9 @@ static int get_feature_levels(int max_fl, int min_fl, return len; } -static IDXGIAdapter1 *get_d3d11_adapter(struct mp_log *log, - struct bstr requested_adapter_name, - struct bstr *listing) +IDXGIAdapter1 *mp_get_dxgi_adapter(struct mp_log *log, + bstr requested_adapter_name, + bstr *listing) { HRESULT hr = S_OK; IDXGIFactory1 *factory; @@ -437,6 +417,37 @@ static IDXGIAdapter1 *get_d3d11_adapter(struct mp_log *log, return picked_adapter; } +int mp_dxgi_validate_adapter(struct mp_log *log, + const struct m_option *opt, + struct bstr name, const char **value) +{ + struct bstr param = bstr0(*value); + bool help = bstr_equals0(param, "help"); + bool adapter_matched = false; + struct bstr listing = { 0 }; + + if (bstr_equals0(param, "")) { + return 0; + } + + adapter_matched = mp_dxgi_list_or_verify_adapters(log, + help ? bstr0(NULL) : param, + help ? &listing : NULL); + + if (help) { + mp_info(log, "Available DXGI adapters:\n%.*s", + BSTR_P(listing)); + talloc_free(listing.start); + return M_OPT_EXIT; + } + + if (!adapter_matched) { + mp_err(log, "No adapter matching '%.*s'!\n", BSTR_P(param)); + } + + return adapter_matched ? 0 : M_OPT_INVALID; +} + static HRESULT create_device(struct mp_log *log, IDXGIAdapter1 *adapter, bool warp, bool debug, int max_fl, int min_fl, ID3D11Device **dev) @@ -455,9 +466,9 @@ static HRESULT create_device(struct mp_log *log, IDXGIAdapter1 *adapter, NULL, flags, levels, levels_len, D3D11_SDK_VERSION, dev, NULL, NULL); } -bool mp_d3d11_list_or_verify_adapters(struct mp_log *log, - bstr adapter_name, - bstr *listing) +bool mp_dxgi_list_or_verify_adapters(struct mp_log *log, + bstr adapter_name, + bstr *listing) { IDXGIAdapter1 *picked_adapter = NULL; @@ -465,7 +476,7 @@ bool mp_d3d11_list_or_verify_adapters(struct mp_log *log, return false; } - if ((picked_adapter = get_d3d11_adapter(log, adapter_name, listing))) { + if ((picked_adapter = mp_get_dxgi_adapter(log, adapter_name, listing))) { SAFE_RELEASE(picked_adapter); return true; } @@ -497,7 +508,7 @@ bool mp_d3d11_create_present_device(struct mp_log *log, goto done; } - adapter = get_d3d11_adapter(log, bstr0(adapter_name), NULL); + adapter = mp_get_dxgi_adapter(log, bstr0(adapter_name), NULL); if (adapter_name && !adapter) { mp_warn(log, "Adapter matching '%s' was not found in the system! " @@ -793,7 +804,7 @@ static bool configure_created_swapchain(struct mp_log *log, IDXGISwapChain *swapchain, DXGI_FORMAT requested_format, DXGI_COLOR_SPACE_TYPE requested_csp, - struct mp_colorspace *configured_csp) + struct pl_color_space *configured_csp) { DXGI_FORMAT probed_format = DXGI_FORMAT_UNKNOWN; DXGI_FORMAT selected_format = DXGI_FORMAT_UNKNOWN; @@ -801,7 +812,7 @@ static bool configure_created_swapchain(struct mp_log *log, DXGI_COLOR_SPACE_TYPE selected_colorspace; const char *format_name = NULL; const char *csp_name = NULL; - struct mp_colorspace mp_csp = { 0 }; + struct pl_color_space pl_color_system = { 0 }; bool mp_csp_mapped = false; query_output_format_and_colorspace(log, swapchain, @@ -817,7 +828,7 @@ static bool configure_created_swapchain(struct mp_log *log, requested_csp : probed_colorspace; format_name = d3d11_get_format_name(selected_format); csp_name = d3d11_get_csp_name(selected_colorspace); - mp_csp_mapped = d3d11_get_mp_csp(selected_colorspace, &mp_csp); + mp_csp_mapped = d3d11_get_mp_csp(selected_colorspace, &pl_color_system); mp_verbose(log, "Selected swapchain format %s (%d), attempting " "to utilize it.\n", @@ -848,7 +859,7 @@ static bool configure_created_swapchain(struct mp_log *log, "mapping! Overriding to standard sRGB!\n", csp_name, selected_colorspace); selected_colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; - d3d11_get_mp_csp(selected_colorspace, &mp_csp); + d3d11_get_mp_csp(selected_colorspace, &pl_color_system); } mp_verbose(log, "Selected swapchain color space %s (%d), attempting to " @@ -860,7 +871,7 @@ static bool configure_created_swapchain(struct mp_log *log, } if (configured_csp) { - *configured_csp = mp_csp; + *configured_csp = pl_color_system; } return true; @@ -964,3 +975,23 @@ done: SAFE_RELEASE(dxgi_dev); return success; } + +bool mp_get_dxgi_output_desc(IDXGISwapChain *swapchain, DXGI_OUTPUT_DESC1 *desc) +{ + bool ret = false; + IDXGIOutput *output = NULL; + IDXGIOutput6 *output6 = NULL; + + if (FAILED(IDXGISwapChain_GetContainingOutput(swapchain, &output))) + goto done; + + if (FAILED(IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput6, (void**)&output6))) + goto done; + + ret = SUCCEEDED(IDXGIOutput6_GetDesc1(output6, desc)); + +done: + SAFE_RELEASE(output); + SAFE_RELEASE(output6); + return ret; +} diff --git a/video/out/gpu/d3d11_helpers.h b/video/out/gpu/d3d11_helpers.h index c115d33..6cc6818 100644 --- a/video/out/gpu/d3d11_helpers.h +++ b/video/out/gpu/d3d11_helpers.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "video/mp_image.h" @@ -65,9 +66,17 @@ struct d3d11_device_opts { char *adapter_name; }; -bool mp_d3d11_list_or_verify_adapters(struct mp_log *log, - bstr adapter_name, - bstr *listing); +IDXGIAdapter1 *mp_get_dxgi_adapter(struct mp_log *log, + bstr requested_adapter_name, + bstr *listing); + +bool mp_get_dxgi_output_desc(IDXGISwapChain *swapchain, DXGI_OUTPUT_DESC1 *desc); + +OPT_STRING_VALIDATE_FUNC(mp_dxgi_validate_adapter); + +bool mp_dxgi_list_or_verify_adapters(struct mp_log *log, + bstr adapter_name, + bstr *listing); bool mp_d3d11_create_present_device(struct mp_log *log, struct d3d11_device_opts *opts, @@ -80,10 +89,10 @@ struct d3d11_swapchain_opts { DXGI_FORMAT format; DXGI_COLOR_SPACE_TYPE color_space; - // mp_colorspace mapping of the configured swapchain colorspace + // pl_color_space mapping of the configured swapchain colorspace // shall be written into this memory location if configuration // succeeds. Will be ignored if NULL. - struct mp_colorspace *configured_csp; + struct pl_color_space *configured_csp; // Use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL if possible bool flip; diff --git a/video/out/gpu/error_diffusion.c b/video/out/gpu/error_diffusion.c index c1ea542..72063c3 100644 --- a/video/out/gpu/error_diffusion.c +++ b/video/out/gpu/error_diffusion.c @@ -227,7 +227,8 @@ void pass_error_diffusion(struct gl_shader_cache *sc, } // Different kernels for error diffusion. -// Patterns are from http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT +// Patterns are from const struct error_diffusion_kernel mp_error_diffusion_kernels[] = { { .name = "simple", diff --git a/video/out/gpu/hwdec.c b/video/out/gpu/hwdec.c index c8098f3..c7d817c 100644 --- a/video/out/gpu/hwdec.c +++ b/video/out/gpu/hwdec.c @@ -34,7 +34,6 @@ extern const struct ra_hwdec_driver ra_hwdec_dxva2gldx; extern const struct ra_hwdec_driver ra_hwdec_d3d11va; extern const struct ra_hwdec_driver ra_hwdec_dxva2dxgi; extern const struct ra_hwdec_driver ra_hwdec_cuda; -extern const struct ra_hwdec_driver ra_hwdec_rpi_overlay; extern const struct ra_hwdec_driver ra_hwdec_drmprime; extern const struct ra_hwdec_driver ra_hwdec_drmprime_overlay; extern const struct ra_hwdec_driver ra_hwdec_aimagereader; @@ -70,9 +69,6 @@ const struct ra_hwdec_driver *const ra_hwdec_drivers[] = { #if HAVE_VDPAU_GL_X11 &ra_hwdec_vdpau, #endif -#if HAVE_RPI_MMAL - &ra_hwdec_rpi_overlay, -#endif #if HAVE_DRM &ra_hwdec_drmprime, &ra_hwdec_drmprime_overlay, diff --git a/video/out/gpu/hwdec.h b/video/out/gpu/hwdec.h index 7766073..f195606 100644 --- a/video/out/gpu/hwdec.h +++ b/video/out/gpu/hwdec.h @@ -18,12 +18,8 @@ struct ra_hwdec_ctx { int num_hwdecs; }; -int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); - -int ra_hwdec_validate_drivers_only_opt(struct mp_log *log, - const m_option_t *opt, - struct bstr name, const char **value); +OPT_STRING_VALIDATE_FUNC(ra_hwdec_validate_opt); +OPT_STRING_VALIDATE_FUNC(ra_hwdec_validate_drivers_only_opt); void ra_hwdec_ctx_init(struct ra_hwdec_ctx *ctx, struct mp_hwdec_devices *devs, const char *opt, bool load_all_by_default); diff --git a/video/out/gpu/lcms.c b/video/out/gpu/lcms.c index 7006a96..c197acf 100644 --- a/video/out/gpu/lcms.c +++ b/video/out/gpu/lcms.c @@ -46,8 +46,8 @@ struct gl_lcms { char *current_profile; bool using_memory_profile; bool changed; - enum mp_csp_prim current_prim; - enum mp_csp_trc current_trc; + enum pl_color_primaries current_prim; + enum pl_color_transfer current_trc; struct mp_log *log; struct mpv_global *global; @@ -162,8 +162,8 @@ static bool vid_profile_eq(struct AVBufferRef *a, struct AVBufferRef *b) // Return whether the profile or config has changed since the last time it was // retrieved. If it has changed, gl_lcms_get_lut3d() should be called. -bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc, struct AVBufferRef *vid_profile) +bool gl_lcms_has_changed(struct gl_lcms *p, enum pl_color_primaries prim, + enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { if (p->changed || p->current_prim != prim || p->current_trc != trc) return true; @@ -180,7 +180,7 @@ bool gl_lcms_has_profile(struct gl_lcms *p) static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, cmsHPROFILE disp_profile, - enum mp_csp_prim prim, enum mp_csp_trc trc) + enum pl_color_primaries prim, enum pl_color_transfer trc) { if (p->opts->use_embedded && p->vid_profile) { // Try using the embedded ICC profile @@ -197,36 +197,41 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, // The input profile for the transformation is dependent on the video // primaries and transfer characteristics - struct mp_csp_primaries csp = mp_get_csp_primaries(prim); - cmsCIExyY wp_xyY = {csp.white.x, csp.white.y, 1.0}; + const struct pl_raw_primaries *csp = pl_raw_primaries_get(prim); + cmsCIExyY wp_xyY = {csp->white.x, csp->white.y, 1.0}; cmsCIExyYTRIPLE prim_xyY = { - .Red = {csp.red.x, csp.red.y, 1.0}, - .Green = {csp.green.x, csp.green.y, 1.0}, - .Blue = {csp.blue.x, csp.blue.y, 1.0}, + .Red = {csp->red.x, csp->red.y, 1.0}, + .Green = {csp->green.x, csp->green.y, 1.0}, + .Blue = {csp->blue.x, csp->blue.y, 1.0}, }; cmsToneCurve *tonecurve[3] = {0}; switch (trc) { - case MP_CSP_TRC_LINEAR: tonecurve[0] = cmsBuildGamma(cms, 1.0); break; - case MP_CSP_TRC_GAMMA18: tonecurve[0] = cmsBuildGamma(cms, 1.8); break; - case MP_CSP_TRC_GAMMA20: tonecurve[0] = cmsBuildGamma(cms, 2.0); break; - case MP_CSP_TRC_GAMMA22: tonecurve[0] = cmsBuildGamma(cms, 2.2); break; - case MP_CSP_TRC_GAMMA24: tonecurve[0] = cmsBuildGamma(cms, 2.4); break; - case MP_CSP_TRC_GAMMA26: tonecurve[0] = cmsBuildGamma(cms, 2.6); break; - case MP_CSP_TRC_GAMMA28: tonecurve[0] = cmsBuildGamma(cms, 2.8); break; - - case MP_CSP_TRC_SRGB: + case PL_COLOR_TRC_LINEAR: tonecurve[0] = cmsBuildGamma(cms, 1.0); break; + case PL_COLOR_TRC_GAMMA18: tonecurve[0] = cmsBuildGamma(cms, 1.8); break; + case PL_COLOR_TRC_GAMMA20: tonecurve[0] = cmsBuildGamma(cms, 2.0); break; + case PL_COLOR_TRC_GAMMA22: tonecurve[0] = cmsBuildGamma(cms, 2.2); break; + case PL_COLOR_TRC_GAMMA24: tonecurve[0] = cmsBuildGamma(cms, 2.4); break; + case PL_COLOR_TRC_GAMMA26: tonecurve[0] = cmsBuildGamma(cms, 2.6); break; + case PL_COLOR_TRC_GAMMA28: tonecurve[0] = cmsBuildGamma(cms, 2.8); break; + + case PL_COLOR_TRC_ST428: + tonecurve[0] = cmsBuildParametricToneCurve(cms, 2, + (double[3]){2.6, pow(52.37/48.0, 1/2.6), 0.0}); + break; + + case PL_COLOR_TRC_SRGB: // Values copied from Little-CMS tonecurve[0] = cmsBuildParametricToneCurve(cms, 4, (double[5]){2.40, 1/1.055, 0.055/1.055, 1/12.92, 0.04045}); break; - case MP_CSP_TRC_PRO_PHOTO: + case PL_COLOR_TRC_PRO_PHOTO: tonecurve[0] = cmsBuildParametricToneCurve(cms, 4, (double[5]){1.8, 1.0, 0.0, 1/16.0, 0.03125}); break; - case MP_CSP_TRC_BT_1886: { + case PL_COLOR_TRC_BT_1886: { double src_black[3]; if (p->opts->contrast < 0) { // User requested infinite contrast, return 2.4 profile @@ -242,7 +247,7 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, // function. Relative colorimetric is used since we want to // approximate the BT.1886 to the target device's actual black // point even in e.g. perceptual mode - const int intent = MP_INTENT_RELATIVE_COLORIMETRIC; + const int intent = PL_INTENT_RELATIVE_COLORIMETRIC; cmsCIEXYZ bp_XYZ; if (!cmsDetectBlackPoint(&bp_XYZ, disp_profile, intent, 0)) return false; @@ -300,7 +305,7 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, } bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, - enum mp_csp_prim prim, enum mp_csp_trc trc, + enum pl_color_primaries prim, enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { int s_r, s_g, s_b; @@ -474,8 +479,8 @@ struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, void gl_lcms_update_options(struct gl_lcms *p) { } bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile) {return false;} -bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc, struct AVBufferRef *vid_profile) +bool gl_lcms_has_changed(struct gl_lcms *p, enum pl_color_primaries prim, + enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { return false; } @@ -486,7 +491,7 @@ bool gl_lcms_has_profile(struct gl_lcms *p) } bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, - enum mp_csp_prim prim, enum mp_csp_trc trc, + enum pl_color_primaries prim, enum pl_color_transfer trc, struct AVBufferRef *vid_profile) { return false; @@ -494,8 +499,7 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, #endif -static int validate_3dlut_size_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(validate_3dlut_size_opt) { int p1, p2, p3; return gl_parse_3dlut_size(*value, &p1, &p2, &p3) ? 0 : M_OPT_INVALID; @@ -519,7 +523,7 @@ const struct m_sub_options mp_icc_conf = { .size = sizeof(struct mp_icc_opts), .defaults = &(const struct mp_icc_opts) { .size_str = "auto", - .intent = MP_INTENT_RELATIVE_COLORIMETRIC, + .intent = PL_INTENT_RELATIVE_COLORIMETRIC, .use_embedded = true, .cache = true, }, diff --git a/video/out/gpu/lcms.h b/video/out/gpu/lcms.h index 607353a..d0b0fe5 100644 --- a/video/out/gpu/lcms.h +++ b/video/out/gpu/lcms.h @@ -37,10 +37,10 @@ void gl_lcms_update_options(struct gl_lcms *p); bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile); bool gl_lcms_has_profile(struct gl_lcms *p); bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **, - enum mp_csp_prim prim, enum mp_csp_trc trc, + enum pl_color_primaries prim, enum pl_color_transfer trc, struct AVBufferRef *vid_profile); -bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc, struct AVBufferRef *vid_profile); +bool gl_lcms_has_changed(struct gl_lcms *p, enum pl_color_primaries prim, + enum pl_color_transfer trc, struct AVBufferRef *vid_profile); static inline bool gl_parse_3dlut_size(const char *arg, int *p1, int *p2, int *p3) { diff --git a/video/out/gpu/libmpv_gpu.c b/video/out/gpu/libmpv_gpu.c index aae1d18..542db7f 100644 --- a/video/out/gpu/libmpv_gpu.c +++ b/video/out/gpu/libmpv_gpu.c @@ -185,7 +185,7 @@ static int render(struct render_backend *ctx, mpv_render_param *params, &(int){0}); struct ra_fbo target = {.tex = tex, .flip = flip}; - gl_video_render_frame(p->renderer, frame, target, RENDER_FRAME_DEF); + gl_video_render_frame(p->renderer, frame, &target, RENDER_FRAME_DEF); p->context->fns->done_frame(p->context, frame->display_synced); return 0; diff --git a/video/out/gpu/osd.c b/video/out/gpu/osd.c index 91505a9..7892904 100644 --- a/video/out/gpu/osd.c +++ b/video/out/gpu/osd.c @@ -286,7 +286,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 ra_fbo fbo) + struct gl_shader_cache *sc, const struct ra_fbo *fbo) { struct mpgl_osd_part *part = ctx->parts[index]; @@ -312,7 +312,7 @@ 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, fbo.tex, false, vertex_vao, MP_ARRAY_SIZE(vertex_vao), + gl_sc_dispatch_draw(sc, fbo->tex, false, vertex_vao, MP_ARRAY_SIZE(vertex_vao), sizeof(struct vertex), part->vertices, part->num_vertices); } diff --git a/video/out/gpu/osd.h b/video/out/gpu/osd.h index 00fbc49..1b05e25 100644 --- a/video/out/gpu/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 ra_fbo fbo); + struct gl_shader_cache *sc, const 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/gpu/ra.h b/video/out/gpu/ra.h index 5f229f8..c0c58ac 100644 --- a/video/out/gpu/ra.h +++ b/video/out/gpu/ra.h @@ -143,8 +143,8 @@ struct ra_tex_params { // be true depends on ra_format.linear_filter) bool src_repeat; // if false, clamp texture coordinates to edge // if true, repeat texture coordinates - bool non_normalized; // hack for GL_TEXTURE_RECTANGLE OSX idiocy - // always set to false, except in OSX code + bool non_normalized; // hack for GL_TEXTURE_RECTANGLE macOS idiocy + // always set to false, except in macOS code bool external_oes; // hack for GL_TEXTURE_EXTERNAL_OES idiocy // If non-NULL, the texture will be created with these contents. Using // this does *not* require setting host_mutable. Otherwise, the initial diff --git a/video/out/gpu/spirv.c b/video/out/gpu/spirv.c index 67088bc..6910049 100644 --- a/video/out/gpu/spirv.c +++ b/video/out/gpu/spirv.c @@ -16,6 +16,7 @@ static const struct spirv_compiler_fns *compilers[] = { #if HAVE_SHADERC [SPIRV_SHADERC] = &spirv_shaderc, #endif + NULL }; static const struct m_opt_choice_alternatives compiler_choices[] = { diff --git a/video/out/gpu/user_shaders.c b/video/out/gpu/user_shaders.c index 708de87..f2507b6 100644 --- a/video/out/gpu/user_shaders.c +++ b/video/out/gpu/user_shaders.c @@ -431,7 +431,7 @@ static bool parse_tex(struct mp_log *log, struct ra *ra, struct bstr *body, void parse_user_shader(struct mp_log *log, struct ra *ra, struct bstr shader, void *priv, - bool (*dohook)(void *p, struct gl_user_shader_hook hook), + bool (*dohook)(void *p, const struct gl_user_shader_hook *hook), bool (*dotex)(void *p, struct gl_user_shader_tex tex)) { if (!dohook || !dotex || !shader.len) @@ -457,7 +457,7 @@ void parse_user_shader(struct mp_log *log, struct ra *ra, struct bstr shader, } struct gl_user_shader_hook h; - if (!parse_hook(log, &shader, &h) || !dohook(priv, h)) + if (!parse_hook(log, &shader, &h) || !dohook(priv, &h)) return; } } diff --git a/video/out/gpu/user_shaders.h b/video/out/gpu/user_shaders.h index 4bb7c22..d3405a8 100644 --- a/video/out/gpu/user_shaders.h +++ b/video/out/gpu/user_shaders.h @@ -88,7 +88,7 @@ struct gl_user_shader_tex { // valid shader block parsed. void parse_user_shader(struct mp_log *log, struct ra *ra, struct bstr shader, void *priv, - bool (*dohook)(void *p, struct gl_user_shader_hook hook), + bool (*dohook)(void *p, const struct gl_user_shader_hook *hook), bool (*dotex)(void *p, struct gl_user_shader_tex tex)); // Evaluate a szexp, given a lookup function for named textures diff --git a/video/out/gpu/utils.c b/video/out/gpu/utils.c index 8a1aacf..d18cf6e 100644 --- a/video/out/gpu/utils.c +++ b/video/out/gpu/utils.c @@ -33,10 +33,10 @@ void gl_transform_trans(struct gl_transform t, struct gl_transform *x) gl_transform_vec(t, &x->t[0], &x->t[1]); } -void gl_transform_ortho_fbo(struct gl_transform *t, struct ra_fbo fbo) +void gl_transform_ortho_fbo(struct gl_transform *t, const 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); + int y_dir = fbo->flip ? -1 : 1; + gl_transform_ortho(t, 0, fbo->tex->params.w, 0, fbo->tex->params.h * y_dir); } float gl_video_scale_ambient_lux(float lmin, float lmax, diff --git a/video/out/gpu/utils.h b/video/out/gpu/utils.h index 215873e..dd52c38 100644 --- a/video/out/gpu/utils.h +++ b/video/out/gpu/utils.h @@ -63,7 +63,7 @@ static inline bool gl_transform_eq(struct gl_transform a, struct gl_transform b) 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); +void gl_transform_ortho_fbo(struct gl_transform *t, const struct ra_fbo *fbo); float gl_video_scale_ambient_lux(float lmin, float lmax, float rmin, float rmax, float lux); diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 852ee78..1478ec4 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -183,6 +183,7 @@ struct gl_video { struct mp_image_params real_image_params; // configured format struct mp_image_params image_params; // texture format (mind hwdec case) + struct mp_image_params target_params; // target format struct ra_imgfmt_desc ra_format; // texture format int plane_count; @@ -212,6 +213,7 @@ struct gl_video { struct ra_tex *merge_tex[4]; struct ra_tex *scale_tex[4]; struct ra_tex *integer_tex[4]; + struct ra_tex *chroma_tex[4]; struct ra_tex *indirect_tex; struct ra_tex *blend_subs_tex; struct ra_tex *error_diffusion_tex[2]; @@ -312,8 +314,8 @@ static const struct gl_video_opts gl_video_opts_def = { .linear_downscaling = true, .sigmoid_upscaling = true, .interpolation_threshold = 0.01, - .alpha_mode = ALPHA_BLEND_TILES, - .background = {0, 0, 0, 255}, + .background = BACKGROUND_TILES, + .background_color = {0, 0, 0, 255}, .gamma = 1.0f, .tone_map = { .curve = TONE_MAPPING_AUTO, @@ -329,14 +331,9 @@ static const struct gl_video_opts gl_video_opts_def = { .hwdec_interop = "auto", }; -static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); - -static int validate_window_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); - -static int validate_error_diffusion_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, const char **value); +static OPT_STRING_VALIDATE_FUNC(validate_scaler_opt); +static OPT_STRING_VALIDATE_FUNC(validate_window_opt); +static OPT_STRING_VALIDATE_FUNC(validate_error_diffusion_opt); #define OPT_BASE_STRUCT struct gl_video_opts @@ -368,13 +365,13 @@ const struct m_sub_options gl_video_conf = { .deprecation_message = "no replacement"}, {"gamma-auto", OPT_BOOL(gamma_auto), .deprecation_message = "no replacement"}, - {"target-prim", OPT_CHOICE_C(target_prim, mp_csp_prim_names)}, - {"target-trc", OPT_CHOICE_C(target_trc, mp_csp_trc_names)}, + {"target-prim", OPT_CHOICE_C(target_prim, pl_csp_prim_names)}, + {"target-trc", OPT_CHOICE_C(target_trc, pl_csp_trc_names)}, {"target-peak", OPT_CHOICE(target_peak, {"auto", 0}), M_RANGE(10, 10000)}, {"target-contrast", OPT_CHOICE(target_contrast, {"auto", 0}, {"inf", -1}), M_RANGE(10, 1000000)}, - {"target-gamut", OPT_CHOICE_C(target_gamut, mp_csp_prim_names)}, + {"target-gamut", OPT_CHOICE_C(target_gamut, pl_csp_prim_names)}, {"tone-mapping", OPT_CHOICE(tone_map.curve, {"auto", TONE_MAPPING_AUTO}, {"clip", TONE_MAPPING_CLIP}, @@ -447,13 +444,12 @@ const struct m_sub_options gl_video_conf = { M_RANGE(1, 128)}, {"error-diffusion", OPT_STRING_VALIDATE(error_diffusion, validate_error_diffusion_opt)}, - {"alpha", OPT_CHOICE(alpha_mode, - {"no", ALPHA_NO}, - {"yes", ALPHA_YES}, - {"blend", ALPHA_BLEND}, - {"blend-tiles", ALPHA_BLEND_TILES})}, + {"background", OPT_CHOICE(background, + {"none", BACKGROUND_NONE}, + {"color", BACKGROUND_COLOR}, + {"tiles", BACKGROUND_TILES})}, {"opengl-rectangle-textures", OPT_BOOL(use_rectangle)}, - {"background", OPT_COLOR(background)}, + {"background-color", OPT_COLOR(background_color)}, {"interpolation", OPT_BOOL(interpolation)}, {"interpolation-threshold", OPT_FLOAT(interpolation_threshold)}, {"blend-subtitles", OPT_CHOICE(blend_subs, @@ -483,6 +479,7 @@ const struct m_sub_options gl_video_conf = { }, .size = sizeof(struct gl_video_opts), .defaults = &gl_video_opts_def, + .change_flags = UPDATE_VIDEO, }; static void uninit_rendering(struct gl_video *p); @@ -578,6 +575,7 @@ static void uninit_rendering(struct gl_video *p) 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]); + ra_tex_free(p->ra, &p->chroma_tex[n]); } ra_tex_free(p->ra, &p->indirect_tex); @@ -605,15 +603,6 @@ bool gl_video_gamma_auto_enabled(struct gl_video *p) return p->opts.gamma_auto; } -struct mp_colorspace gl_video_get_output_colorspace(struct gl_video *p) -{ - return (struct mp_colorspace) { - .primaries = p->opts.target_prim, - .gamma = p->opts.target_trc, - .hdr.max_luma = p->opts.target_peak, - }; -} - // Warning: profile.start must point to a ta allocation, and the function // takes over ownership. void gl_video_set_icc_profile(struct gl_video *p, bstr icc_data) @@ -627,8 +616,8 @@ bool gl_video_icc_auto_enabled(struct gl_video *p) return p->opts.icc_opts ? p->opts.icc_opts->profile_auto : false; } -static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim, - enum mp_csp_trc trc) +static bool gl_video_get_lut3d(struct gl_video *p, enum pl_color_primaries prim, + enum pl_color_transfer trc) { if (!p->use_lut_3d) return false; @@ -771,16 +760,16 @@ static void pass_get_images(struct gl_video *p, struct video_image *vimg, struct gl_transform chroma = {{{ls_w, 0.0}, {0.0, ls_h}}}; - if (p->image_params.chroma_location != MP_CHROMA_CENTER) { - int cx, cy; - mp_get_chroma_location(p->image_params.chroma_location, &cx, &cy); + if (p->image_params.chroma_location != PL_CHROMA_CENTER) { + float cx, cy; + pl_chroma_location_offset(p->image_params.chroma_location, &cx, &cy); // By default texture coordinates are such that chroma is centered with // any chroma subsampling. If a specific direction is given, make it // so that the luma and chroma sample line up exactly. // For 4:4:4, setting chroma location should have no effect at all. // luma sample size (in chroma coord. space) - chroma.t[0] = ls_w < 1 ? ls_w * -cx / 2 : 0; - chroma.t[1] = ls_h < 1 ? ls_h * -cy / 2 : 0; + chroma.t[0] = ls_w < 1 ? ls_w * -cx : 0; + chroma.t[1] = ls_h < 1 ? ls_h * -cy : 0; } memset(img, 0, 4 * sizeof(img[0])); @@ -796,9 +785,9 @@ static void pass_get_images(struct gl_video *p, struct video_image *vimg, ctype = PLANE_NONE; } else if (c == 4) { ctype = PLANE_ALPHA; - } else if (p->image_params.color.space == MP_CSP_RGB) { + } else if (p->image_params.repr.sys == PL_COLOR_SYSTEM_RGB) { ctype = PLANE_RGB; - } else if (p->image_params.color.space == MP_CSP_XYZ) { + } else if (p->image_params.repr.sys == PL_COLOR_SYSTEM_XYZ) { ctype = PLANE_XYZ; } else { ctype = c == 1 ? PLANE_LUMA : PLANE_CHROMA; @@ -810,7 +799,7 @@ static void pass_get_images(struct gl_video *p, struct video_image *vimg, int msb_valid_bits = p->ra_format.component_bits + MPMIN(p->ra_format.component_pad, 0); - int csp = type == PLANE_ALPHA ? MP_CSP_RGB : p->image_params.color.space; + int csp = type == PLANE_ALPHA ? PL_COLOR_SYSTEM_RGB : p->image_params.repr.sys; float tex_mul = 1.0 / mp_get_csp_mul(csp, msb_valid_bits, p->ra_format.component_bits); if (p->ra_format.component_type == RA_CTYPE_FLOAT) @@ -1065,13 +1054,13 @@ static void uninit_video(struct gl_video *p) ra_hwdec_mapper_free(&p->hwdec_mapper); } -static void pass_record(struct gl_video *p, struct mp_pass_perf perf) +static void pass_record(struct gl_video *p, const struct mp_pass_perf *perf) { if (!p->pass || p->pass_idx == VO_PASS_PERF_MAX) return; struct pass_info *pass = &p->pass[p->pass_idx]; - pass->perf = perf; + pass->perf = *perf; if (pass->desc.len == 0) bstr_xappend(p, &pass->desc, bstr0("(unknown)")); @@ -1211,12 +1200,13 @@ static void dispatch_compute(struct gl_video *p, int w, int h, if (!(p->ra->caps & RA_CAP_NUM_GROUPS)) PRELUDE("#define gl_NumWorkGroups uvec3(%d, %d, 1)\n", num_x, num_y); - pass_record(p, gl_sc_dispatch_compute(p->sc, num_x, num_y, 1)); + struct mp_pass_perf perf = gl_sc_dispatch_compute(p->sc, num_x, num_y, 1); + pass_record(p, &perf); cleanup_binds(p); } static struct mp_pass_perf render_pass_quad(struct gl_video *p, - struct ra_fbo fbo, bool discard, + const struct ra_fbo *fbo, bool discard, const struct mp_rect *dst) { // The first element is reserved for `vec2 position` @@ -1274,15 +1264,16 @@ static struct mp_pass_perf render_pass_quad(struct gl_video *p, &p->tmp_vertex[num_vertex_attribs * 1], vertex_stride); - return gl_sc_dispatch_draw(p->sc, fbo.tex, discard, p->vao, num_vertex_attribs, + return gl_sc_dispatch_draw(p->sc, fbo->tex, discard, p->vao, num_vertex_attribs, vertex_stride, p->tmp_vertex, num_vertices); } -static void finish_pass_fbo(struct gl_video *p, struct ra_fbo fbo, +static void finish_pass_fbo(struct gl_video *p, const struct ra_fbo *fbo, bool discard, const struct mp_rect *dst) { pass_prepare_src_tex(p); - pass_record(p, render_pass_quad(p, fbo, discard, dst)); + struct mp_pass_perf perf = render_pass_quad(p, fbo, discard, dst); + pass_record(p, &perf); debug_check_gl(p, "after rendering"); cleanup_binds(p); } @@ -1319,7 +1310,7 @@ static void finish_pass_tex(struct gl_video *p, struct ra_tex **dst_tex, debug_check_gl(p, "after dispatching compute shader"); } else { struct ra_fbo fbo = { .tex = *dst_tex, }; - finish_pass_fbo(p, fbo, true, &(struct mp_rect){0, 0, w, h}); + finish_pass_fbo(p, &fbo, true, &(struct mp_rect){0, 0, w, h}); } } @@ -1955,7 +1946,7 @@ static void deband_hook(struct gl_video *p, struct image img, { 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); + p->image_params.color.transfer); } static void unsharp_hook(struct gl_video *p, struct image img, @@ -2046,25 +2037,23 @@ static void user_hook(struct gl_video *p, struct image img, gl_transform_trans(shader->offset, trans); } -static bool add_user_hook(void *priv, struct gl_user_shader_hook hook) +static bool add_user_hook(void *priv, const struct gl_user_shader_hook *hook) { struct gl_video *p = priv; - struct gl_user_shader_hook *copy = talloc_ptrtype(p, copy); - *copy = hook; - + struct gl_user_shader_hook *copy = talloc_dup(p, (struct gl_user_shader_hook *)hook); struct tex_hook texhook = { - .save_tex = bstrdup0(copy, hook.save_tex), - .components = hook.components, - .align_offset = hook.align_offset, + .save_tex = bstrdup0(copy, copy->save_tex), + .components = copy->components, + .align_offset = copy->align_offset, .hook = user_hook, .cond = user_hook_cond, .priv = copy, }; for (int h = 0; h < SHADER_MAX_HOOKS; h++) - texhook.hook_tex[h] = bstrdup0(copy, hook.hook_tex[h]); + texhook.hook_tex[h] = bstrdup0(copy, copy->hook_tex[h]); for (int h = 0; h < SHADER_MAX_BINDS; h++) - texhook.bind_tex[h] = bstrdup0(copy, hook.bind_tex[h]); + texhook.bind_tex[h] = bstrdup0(copy, copy->bind_tex[h]); MP_TARRAY_APPEND(p, p->tex_hooks, p->num_tex_hooks, texhook); return true; @@ -2213,6 +2202,23 @@ static void pass_read_video(struct gl_video *p) } } + // If chroma textures are in a subsampled semi-planar format and rotated, + // introduce an explicit conversion pass to avoid breaking chroma scalers. + for (int n = 0; n < 4; n++) { + if (img[n].tex && img[n].type == PLANE_CHROMA && + img[n].tex->params.format->num_components == 2 && + p->image_params.rotate % 180 == 90 && + p->ra_format.chroma_w != 1) + { + GLSLF("// chroma fix for rotated plane %d\n", n); + copy_image(p, &(int){0}, img[n]); + pass_describe(p, "chroma fix for rotated plane"); + finish_pass_tex(p, &p->chroma_tex[n], img[n].w, img[n].h); + img[n] = image_wrap(p->chroma_tex[n], img[n].type, + img[n].components); + } + } + // At this point all planes are finalized but they may not be at the // required size yet. Furthermore, they may have texture offsets that // require realignment. @@ -2345,29 +2351,29 @@ static void pass_convert_yuv(struct gl_video *p) GLSLF("color = color.%s;\n", p->color_swizzle); // Pre-colormatrix input gamma correction - if (cparams.color.space == MP_CSP_XYZ) - pass_linearize(p->sc, p->image_params.color.gamma); + if (cparams.repr.sys == PL_COLOR_SYSTEM_XYZ) + pass_linearize(p->sc, p->image_params.color.transfer); // We always explicitly normalize the range in pass_read_video cparams.input_bits = cparams.texture_bits = 0; // Conversion to RGB. For RGB itself, this still applies e.g. brightness // and contrast controls, or expansion of e.g. LSB-packed 10 bit data. - struct mp_cmat m = {{{0}}}; + struct pl_transform3x3 m = {0}; mp_get_csp_matrix(&cparams, &m); - gl_sc_uniform_mat3(sc, "colormatrix", true, &m.m[0][0]); + gl_sc_uniform_mat3(sc, "colormatrix", true, &m.mat.m[0][0]); gl_sc_uniform_vec3(sc, "colormatrix_c", m.c); GLSL(color.rgb = mat3(colormatrix) * color.rgb + colormatrix_c;) - if (cparams.color.space == MP_CSP_XYZ) { - pass_delinearize(p->sc, p->image_params.color.gamma); + if (cparams.repr.sys == PL_COLOR_SYSTEM_XYZ) { + pass_delinearize(p->sc, p->image_params.color.transfer); // mp_get_csp_matrix implicitly converts XYZ to DCI-P3 - p->image_params.color.space = MP_CSP_RGB; - p->image_params.color.primaries = MP_CSP_PRIM_DCI_P3; + p->image_params.repr.sys = PL_COLOR_SYSTEM_RGB; + p->image_params.color.primaries = PL_COLOR_PRIM_DCI_P3; } - if (p->image_params.color.space == MP_CSP_BT_2020_C) { + if (p->image_params.repr.sys == PL_COLOR_SYSTEM_BT_2020_C) { // Conversion for C'rcY'cC'bc via the BT.2020 CL system: // C'bc = (B'-Y'c) / 1.9404 | C'bc <= 0 // = (B'-Y'c) / 1.5816 | C'bc > 0 @@ -2404,9 +2410,9 @@ static void pass_convert_yuv(struct gl_video *p) } p->components = 3; - if (!p->has_alpha || p->opts.alpha_mode == ALPHA_NO) { + if (!p->has_alpha) { GLSL(color.a = 1.0;) - } else if (p->image_params.alpha == MP_ALPHA_PREMUL) { + } else if (p->image_params.repr.alpha == PL_ALPHA_PREMULTIPLIED) { p->components = 4; } else { p->components = 4; @@ -2491,7 +2497,7 @@ static void pass_scale_main(struct gl_video *p) // Linear light downscaling results in nasty artifacts for HDR curves // due to the potentially extreme brightness differences severely // compounding any ringing. So just scale in gamma light instead. - if (mp_trc_is_hdr(p->image_params.color.gamma)) + if (pl_color_space_is_hdr(&p->image_params.color)) use_linear = false; } else if (upscaling) { use_linear = p->opts.linear_upscaling || p->opts.sigmoid_upscaling; @@ -2499,7 +2505,7 @@ static void pass_scale_main(struct gl_video *p) if (use_linear) { p->use_linear = true; - pass_linearize(p->sc, p->image_params.color.gamma); + pass_linearize(p->sc, p->image_params.color.transfer); pass_opt_hook_point(p, "LINEAR", NULL); } @@ -2552,8 +2558,9 @@ static void pass_scale_main(struct gl_video *p) // rendering) // If OSD is true, ignore any changes that may have been made to the video // by previous passes (i.e. linear scaling) -static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, - struct mp_colorspace fbo_csp, int flags, bool osd) +static void pass_colormanage(struct gl_video *p, struct pl_color_space src, + enum mp_csp_light src_light, + struct pl_color_space fbo_csp, int flags, bool osd) { struct ra *ra = p->ra; @@ -2561,18 +2568,17 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, // unless specific transfer function, primaries or target peak // is set. If values are set to _AUTO, the most likely intended // values are guesstimated later in this function. - struct mp_colorspace dst = { - .gamma = p->opts.target_trc == MP_CSP_TRC_AUTO ? - fbo_csp.gamma : p->opts.target_trc, - .primaries = p->opts.target_prim == MP_CSP_PRIM_AUTO ? + struct pl_color_space dst = { + .transfer = p->opts.target_trc == PL_COLOR_TRC_UNKNOWN ? + fbo_csp.transfer : p->opts.target_trc, + .primaries = p->opts.target_prim == PL_COLOR_PRIM_UNKNOWN ? fbo_csp.primaries : p->opts.target_prim, - .light = MP_CSP_LIGHT_DISPLAY, .hdr.max_luma = !p->opts.target_peak ? fbo_csp.hdr.max_luma : p->opts.target_peak, }; if (!p->colorspace_override_warned && - ((fbo_csp.gamma && dst.gamma != fbo_csp.gamma) || + ((fbo_csp.transfer && dst.transfer != fbo_csp.transfer) || (fbo_csp.primaries && dst.primaries != fbo_csp.primaries))) { MP_WARN(p, "One or more colorspace value is being overridden " @@ -2580,44 +2586,44 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, "transfer function: (dst: %s, fbo: %s), " "primaries: (dst: %s, fbo: %s). " "Rendering can lead to incorrect results!\n", - m_opt_choice_str(mp_csp_trc_names, dst.gamma), - m_opt_choice_str(mp_csp_trc_names, fbo_csp.gamma), - m_opt_choice_str(mp_csp_prim_names, dst.primaries), - m_opt_choice_str(mp_csp_prim_names, fbo_csp.primaries)); + m_opt_choice_str(pl_csp_trc_names, dst.transfer), + m_opt_choice_str(pl_csp_trc_names, fbo_csp.transfer), + m_opt_choice_str(pl_csp_prim_names, dst.primaries), + m_opt_choice_str(pl_csp_prim_names, fbo_csp.primaries)); p->colorspace_override_warned = true; } - if (dst.gamma == MP_CSP_TRC_HLG) - dst.light = MP_CSP_LIGHT_SCENE_HLG; + enum mp_csp_light dst_light = dst.transfer == PL_COLOR_TRC_HLG ? + MP_CSP_LIGHT_SCENE_HLG : MP_CSP_LIGHT_DISPLAY; if (p->use_lut_3d && (flags & RENDER_SCREEN_COLOR)) { // The 3DLUT is always generated against the video's original source // space, *not* the reference space. (To avoid having to regenerate // the 3DLUT for the OSD on every frame) - enum mp_csp_prim prim_orig = p->image_params.color.primaries; - enum mp_csp_trc trc_orig = p->image_params.color.gamma; + enum pl_color_primaries prim_orig = p->image_params.color.primaries; + enum pl_color_transfer trc_orig = p->image_params.color.transfer; // One exception: HDR is not implemented by LittleCMS for technical // limitation reasons, so we use a gamma 2.2 input curve here instead. // We could pick any value we want here, the difference is just coding // efficiency. - if (mp_trc_is_hdr(trc_orig)) - trc_orig = MP_CSP_TRC_GAMMA22; + if (pl_color_space_is_hdr(&p->image_params.color)) + trc_orig = PL_COLOR_TRC_GAMMA22; if (gl_video_get_lut3d(p, prim_orig, trc_orig)) { dst.primaries = prim_orig; - dst.gamma = trc_orig; - assert(dst.primaries && dst.gamma); + dst.transfer = trc_orig; + assert(dst.primaries && dst.transfer); } } - if (dst.primaries == MP_CSP_PRIM_AUTO) { + if (dst.primaries == PL_COLOR_PRIM_UNKNOWN) { // The vast majority of people are on sRGB or BT.709 displays, so pick // this as the default output color space. - dst.primaries = MP_CSP_PRIM_BT_709; + dst.primaries = PL_COLOR_PRIM_BT_709; - if (src.primaries == MP_CSP_PRIM_BT_601_525 || - src.primaries == MP_CSP_PRIM_BT_601_625) + if (src.primaries == PL_COLOR_PRIM_BT_601_525 || + src.primaries == PL_COLOR_PRIM_BT_601_625) { // Since we auto-pick BT.601 and BT.709 based on the dimensions, // combined with the fact that they're very similar to begin with, @@ -2627,28 +2633,28 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, } } - if (dst.gamma == MP_CSP_TRC_AUTO) { + if (dst.transfer == PL_COLOR_TRC_UNKNOWN) { // Most people seem to complain when the image is darker or brighter // than what they're "used to", so just avoid changing the gamma // altogether by default. The only exceptions to this rule apply to // very unusual TRCs, which even hardcode technoluddites would probably // not enjoy viewing unaltered. - dst.gamma = src.gamma; + dst.transfer = src.transfer; // Avoid outputting linear light or HDR content "by default". For these // just pick gamma 2.2 as a default, since it's a good estimate for // the response of typical displays - if (dst.gamma == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(dst.gamma)) - dst.gamma = MP_CSP_TRC_GAMMA22; + if (dst.transfer == PL_COLOR_TRC_LINEAR || pl_color_space_is_hdr(&dst)) + dst.transfer = PL_COLOR_TRC_GAMMA22; } // If there's no specific signal peak known for the output display, infer // it from the chosen transfer function. Also normalize the src peak, in // case it was unknown if (!dst.hdr.max_luma) - dst.hdr.max_luma = mp_trc_nom_peak(dst.gamma) * MP_REF_WHITE; + dst.hdr.max_luma = pl_color_transfer_nominal_peak(dst.transfer) * MP_REF_WHITE; if (!src.hdr.max_luma) - src.hdr.max_luma = mp_trc_nom_peak(src.gamma) * MP_REF_WHITE; + src.hdr.max_luma = pl_color_transfer_nominal_peak(src.transfer) * MP_REF_WHITE; // Whitelist supported modes switch (p->opts.tone_map.curve) { @@ -2680,7 +2686,7 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, } struct gl_tone_map_opts tone_map = p->opts.tone_map; - bool detect_peak = tone_map.compute_peak >= 0 && mp_trc_is_hdr(src.gamma) + bool detect_peak = tone_map.compute_peak >= 0 && pl_color_space_is_hdr(&src) && src.hdr.max_luma > dst.hdr.max_luma; if (detect_peak && !p->hdr_peak_ssbo) { @@ -2719,7 +2725,22 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, } // Adapt from src to dst as necessary - pass_color_map(p->sc, p->use_linear && !osd, src, dst, &tone_map); + pass_color_map(p->sc, p->use_linear && !osd, src, dst, src_light, dst_light, &tone_map); + + if (!osd) { + struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; + mp_csp_equalizer_state_get(p->video_eq, &cparams); + if (cparams.levels_out == PL_COLOR_LEVELS_UNKNOWN) + cparams.levels_out = PL_COLOR_LEVELS_FULL; + p->target_params = (struct mp_image_params){ + .imgfmt_name = p->fbo_format ? p->fbo_format->name : "unknown", + .w = p->texture_w, + .h = p->texture_h, + .color = dst, + .repr = {.sys = PL_COLOR_SYSTEM_RGB, .levels = cparams.levels_out}, + .rotate = p->image_params.rotate, + }; + } if (p->use_lut_3d && (flags & RENDER_SCREEN_COLOR)) { gl_sc_uniform_texture(p->sc, "lut_3d", p->lut_3d_texture); @@ -2735,7 +2756,7 @@ void gl_video_set_fb_depth(struct gl_video *p, int fb_depth) p->fb_depth = fb_depth; } -static void pass_dither(struct gl_video *p) +static void pass_dither(struct gl_video *p, const struct ra_fbo *fbo) { // Assume 8 bits per component if unknown. int dst_depth = p->fb_depth > 0 ? p->fb_depth : 8; @@ -2868,7 +2889,9 @@ static void pass_dither(struct gl_video *p) gl_sc_uniform_texture(p->sc, "dither", p->dither_texture); - GLSLF("vec2 dither_pos = gl_FragCoord.xy * 1.0/%d.0;\n", dither_size); + GLSLF("vec2 dither_coord = vec2(gl_FragCoord.x, %d.0 + %f * gl_FragCoord.y);", + fbo->flip ? fbo->tex->params.h : 0, fbo->flip ? -1.0 : 1.0); + GLSLF("vec2 dither_pos = dither_coord * 1.0/%d.0;\n", dither_size); if (p->opts.temporal_dither) { int phase = (p->frames_rendered / p->opts.temporal_dither_period) % 8u; @@ -2891,7 +2914,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 osd_flags, int frame_flags, - double pts, struct mp_osd_res rect, struct ra_fbo fbo, + double pts, struct mp_osd_res rect, const struct ra_fbo *fbo, bool cms) { if (frame_flags & RENDER_FRAME_VF_SUBS) @@ -2910,20 +2933,21 @@ static void pass_draw_osd(struct gl_video *p, int osd_flags, int frame_flags, // When subtitles need to be color managed, assume they're in sRGB // (for lack of anything saner to do) if (cms) { - static const struct mp_colorspace csp_srgb = { - .primaries = MP_CSP_PRIM_BT_709, - .gamma = MP_CSP_TRC_SRGB, - .light = MP_CSP_LIGHT_DISPLAY, + static const struct pl_color_space csp_srgb = { + .primaries = PL_COLOR_PRIM_BT_709, + .transfer = PL_COLOR_TRC_SRGB, }; - pass_colormanage(p, csp_srgb, fbo.color_space, frame_flags, true); + pass_colormanage(p, csp_srgb, MP_CSP_LIGHT_DISPLAY, fbo->color_space, + frame_flags, true); } mpgl_osd_draw_finish(p->osd, n, p->sc, fbo); } timer_pool_stop(p->osd_timer); pass_describe(p, "drawing osd"); - pass_record(p, timer_pool_measure(p->osd_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->osd_timer); + pass_record(p, &perf); } static float chroma_realign(int size, int pixel) @@ -3013,7 +3037,7 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, }; 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, flags, vpts, rect, fbo, false); + pass_draw_osd(p, OSD_DRAW_SUB_ONLY, flags, vpts, rect, &fbo, false); pass_read_tex(p, p->blend_subs_tex); pass_describe(p, "blend subs video"); } @@ -3040,12 +3064,12 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, rect.mt *= scale[1]; rect.mb *= scale[1]; // We should always blend subtitles in non-linear light if (p->use_linear) { - pass_delinearize(p->sc, p->image_params.color.gamma); + pass_delinearize(p->sc, p->image_params.color.transfer); p->use_linear = false; } 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, flags, vpts, rect, fbo, false); + pass_draw_osd(p, OSD_DRAW_SUB_ONLY, flags, vpts, rect, &fbo, false); pass_read_tex(p, p->blend_subs_tex); pass_describe(p, "blend subs"); } @@ -3055,7 +3079,7 @@ static bool pass_render_frame(struct gl_video *p, struct mp_image *mpi, return true; } -static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo, int flags) +static void pass_draw_to_screen(struct gl_video *p, const struct ra_fbo *fbo, int flags) { if (p->dumb_mode) pass_render_frame_dumb(p); @@ -3067,7 +3091,8 @@ static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo, int flags GLSL(color.rgb = pow(color.rgb, vec3(user_gamma));) } - pass_colormanage(p, p->image_params.color, fbo.color_space, flags, false); + pass_colormanage(p, p->image_params.color, p->image_params.light, + fbo->color_space, flags, false); // Since finish_pass_fbo doesn't work with compute shaders, and neither // does the checkerboard/dither code, we may need an indirection via @@ -3080,28 +3105,30 @@ static void pass_draw_to_screen(struct gl_video *p, struct ra_fbo fbo, int flags copy_image(p, &(int){0}, tmp); } - if (p->has_alpha){ - if (p->opts.alpha_mode == ALPHA_BLEND_TILES) { + if (p->has_alpha) { + if (p->opts.background == BACKGROUND_TILES) { // 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));) + GLSLF("vec2 tile_coord = vec2(gl_FragCoord.x, %d.0 + %f * gl_FragCoord.y);", + fbo->flip ? fbo->tex->params.h : 0, fbo->flip ? -1.0 : 1.0); + GLSL(bvec2 tile = lessThan(fract(tile_coord * 1.0 / 32.0), vec2(0.5));) 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) { + } else if (p->opts.background == BACKGROUND_COLOR) { // Blend into background color (usually black) - struct m_color c = p->opts.background; + struct m_color c = p->opts.background_color; 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.rgb += background.rgb * (1.0 - color.a);) - GLSL(color.a = background.a;) + GLSL(color += background * (1.0 - color.a);) + GLSL(color.rgb *= vec3(color.a);); } } pass_opt_hook_point(p, "OUTPUT", NULL); if (flags & RENDER_SCREEN_COLOR) - pass_dither(p); + pass_dither(p, fbo); pass_describe(p, "output to screen"); finish_pass_fbo(p, fbo, false, &p->dst_rect); } @@ -3122,7 +3149,7 @@ static bool update_surface(struct gl_video *p, struct mp_image *mpi, // because mixing in compressed light artificially darkens the results if (!p->use_linear) { p->use_linear = true; - pass_linearize(p->sc, p->image_params.color.gamma); + pass_linearize(p->sc, p->image_params.color.transfer); } finish_pass_tex(p, &surf->tex, vp_w, vp_h); @@ -3134,7 +3161,7 @@ static bool update_surface(struct gl_video *p, struct mp_image *mpi, // Draws an interpolate frame to fbo, based on the frame timing in t // flags: bit set of RENDER_FRAME_* flags static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, - struct ra_fbo fbo, int flags) + const struct ra_fbo *fbo, int flags) { bool is_new = false; @@ -3201,7 +3228,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, struct mp_image *f = t->frames[i]; uint64_t f_id = t->frame_id + i; - if (!mp_image_params_equal(&f->params, &p->real_image_params)) + if (!mp_image_params_static_equal(&f->params, &p->real_image_params)) continue; if (f_id > p->surfaces[p->surface_idx].id) { @@ -3306,11 +3333,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 ra_fbo fbo, int flags) + const struct ra_fbo *fbo, int flags) { gl_video_update_options(p); - struct mp_rect target_rc = {0, 0, fbo.tex->params.w, fbo.tex->params.h}; + struct mp_rect target_rc = {0, 0, fbo->tex->params.w, fbo->tex->params.h}; p->broken_frame = false; @@ -3318,12 +3345,15 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, struct m_color c = p->clear_color; float clear_color[4] = {c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0}; - p->ra->fns->clear(p->ra, fbo.tex, clear_color, &target_rc); + clear_color[0] *= clear_color[3]; + clear_color[1] *= clear_color[3]; + clear_color[2] *= clear_color[3]; + p->ra->fns->clear(p->ra, fbo->tex, clear_color, &target_rc); if (p->hwdec_overlay) { if (has_frame) { float *color = p->hwdec_overlay->overlay_colorkey; - p->ra->fns->clear(p->ra, fbo.tex, color, &p->dst_rect); + p->ra->fns->clear(p->ra, fbo->tex, color, &p->dst_rect); } p->hwdec_overlay->driver->overlay_frame(p->hwdec_overlay, frame->current, @@ -3364,43 +3394,41 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, // For the non-interpolation case, we draw to a single "cache" // texture to speed up subsequent re-draws (if any exist) - struct ra_fbo dest_fbo = fbo; bool repeats = frame->num_vsyncs > 1 && frame->display_synced; + bool r = false; if ((repeats || frame->still) && !p->dumb_mode && - (p->ra->caps & RA_CAP_BLIT) && fbo.tex->params.blit_dst) + (p->ra->caps & RA_CAP_BLIT) && fbo->tex->params.blit_dst) { // Attempt to use the same format as the destination FBO // if possible. Some RAs use a wrapped dummy format here, // so fall back to the fbo_format in that case. - const struct ra_format *fmt = fbo.tex->params.format; + const struct ra_format *fmt = fbo->tex->params.format; if (fmt->dummy_format) fmt = p->fbo_format; - - bool r = ra_tex_resize(p->ra, p->log, &p->output_tex, - fbo.tex->params.w, fbo.tex->params.h, - fmt); - if (r) { - dest_fbo = (struct ra_fbo) { p->output_tex }; - p->output_tex_valid = true; - } + r = ra_tex_resize(p->ra, p->log, &p->output_tex, + fbo->tex->params.w, fbo->tex->params.h, + fmt); } + const struct ra_fbo *dest_fbo = r ? &(struct ra_fbo) { p->output_tex } : fbo; + p->output_tex_valid = r; pass_draw_to_screen(p, dest_fbo, flags); } // "output tex valid" and "output tex needed" are equivalent - if (p->output_tex_valid && fbo.tex->params.blit_dst) { + if (p->output_tex_valid && fbo->tex->params.blit_dst) { pass_info_reset(p, true); pass_describe(p, "redraw cached frame"); struct mp_rect src = p->dst_rect; struct mp_rect dst = src; - if (fbo.flip) { - dst.y0 = fbo.tex->params.h - src.y0; - dst.y1 = fbo.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, fbo.tex, p->output_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)); + struct mp_pass_perf perf = timer_pool_measure(p->blit_timer); + pass_record(p, &perf); } } } @@ -3431,7 +3459,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, fbo.tex, color, &target_rc); + p->ra->fns->clear(p->ra, fbo->tex, color, &target_rc); } p->frames_rendered++; @@ -3522,7 +3550,7 @@ void gl_video_screenshot(struct gl_video *p, struct vo_frame *frame, flags |= RENDER_FRAME_OSD; if (args->scaled) flags |= RENDER_SCREEN_COLOR; - gl_video_render_frame(p, nframe, (struct ra_fbo){target}, flags); + gl_video_render_frame(p, nframe, &(struct ra_fbo){target}, flags); res = mp_image_alloc(mpfmt, params.w, params.h); if (!res) @@ -3636,7 +3664,8 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t timer_pool_start(p->upload_timer); bool ok = ra_hwdec_mapper_map(p->hwdec_mapper, vimg->mpi) >= 0; timer_pool_stop(p->upload_timer); - pass_record(p, timer_pool_measure(p->upload_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->upload_timer); + pass_record(p, &perf); vimg->hwdec_mapped = true; if (ok) { @@ -3708,7 +3737,8 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t bool using_pbo = p->ra->use_pbo || !(p->ra->caps & RA_CAP_DIRECT_UPLOAD); const char *mode = p->using_dr_path ? "DR" : using_pbo ? "PBO" : "naive"; pass_describe(p, "upload frame (%s)", mode); - pass_record(p, timer_pool_measure(p->upload_timer)); + struct mp_pass_perf perf = timer_pool_measure(p->upload_timer); + pass_record(p, &perf); return true; @@ -3806,9 +3836,8 @@ static void check_gl_features(struct gl_video *p) p->opts.dither_algo = DITHER_NONE; MP_WARN(p, "Disabling dithering (no gl_FragCoord).\n"); } - if (!have_fragcoord && p->opts.alpha_mode == ALPHA_BLEND_TILES) { - p->opts.alpha_mode = ALPHA_BLEND; - // Verbose, since this is the default setting + if (!have_fragcoord && p->opts.background == BACKGROUND_TILES) { + p->opts.background = BACKGROUND_COLOR; MP_VERBOSE(p, "Disabling alpha checkerboard (no gl_FragCoord).\n"); } if (!have_fbo && have_compute) { @@ -3864,9 +3893,9 @@ static void check_gl_features(struct gl_video *p) .gamma_auto = p->opts.gamma_auto, .pbo = p->opts.pbo, .fbo_format = p->opts.fbo_format, - .alpha_mode = p->opts.alpha_mode, - .use_rectangle = p->opts.use_rectangle, .background = p->opts.background, + .use_rectangle = p->opts.use_rectangle, + .background_color = p->opts.background_color, .dither_algo = p->opts.dither_algo, .dither_depth = p->opts.dither_depth, .dither_size = p->opts.dither_size, @@ -3913,8 +3942,8 @@ static void check_gl_features(struct gl_video *p) } } - int use_cms = p->opts.target_prim != MP_CSP_PRIM_AUTO || - p->opts.target_trc != MP_CSP_TRC_AUTO || p->use_lut_3d; + int use_cms = p->opts.target_prim != PL_COLOR_PRIM_UNKNOWN || + p->opts.target_trc != PL_COLOR_TRC_UNKNOWN || p->use_lut_3d; // mix() is needed for some gamma functions if (!have_mglsl && (p->opts.linear_downscaling || @@ -3926,8 +3955,8 @@ static void check_gl_features(struct gl_video *p) MP_WARN(p, "Disabling linear/sigmoid scaling (GLSL version too old).\n"); } if (!have_mglsl && use_cms) { - p->opts.target_prim = MP_CSP_PRIM_AUTO; - p->opts.target_trc = MP_CSP_TRC_AUTO; + p->opts.target_prim = PL_COLOR_PRIM_UNKNOWN; + p->opts.target_trc = PL_COLOR_TRC_UNKNOWN; p->use_lut_3d = false; MP_WARN(p, "Disabling color management (GLSL version too old).\n"); } @@ -4022,7 +4051,7 @@ void gl_video_config(struct gl_video *p, struct mp_image_params *params) unmap_overlay(p); unref_current_image(p); - if (!mp_image_params_equal(&p->real_image_params, params)) { + if (!mp_image_params_static_equal(&p->real_image_params, params)) { uninit_video(p); p->real_image_params = *params; p->image_params = *params; @@ -4117,7 +4146,7 @@ static void reinit_from_options(struct gl_video *p) p->opts = *(struct gl_video_opts *)p->opts_cache->opts; if (!p->force_clear_color) - p->clear_color = p->opts.background; + p->clear_color = p->opts.background_color; check_gl_features(p); uninit_rendering(p); @@ -4362,3 +4391,8 @@ void gl_video_load_hwdecs_for_img_fmt(struct gl_video *p, struct mp_hwdec_device assert(p->hwdec_ctx.ra_ctx); ra_hwdec_ctx_load_fmt(&p->hwdec_ctx, devs, params); } + +struct mp_image_params *gl_video_get_target_params_ptr(struct gl_video *p) +{ + return &p->target_params; +} diff --git a/video/out/gpu/video.h b/video/out/gpu/video.h index 411d336..66ccd9c 100644 --- a/video/out/gpu/video.h +++ b/video/out/gpu/video.h @@ -72,11 +72,10 @@ enum dither_algo { DITHER_ERROR_DIFFUSION, }; -enum alpha_mode { - ALPHA_NO = 0, - ALPHA_YES, - ALPHA_BLEND, - ALPHA_BLEND_TILES, +enum background_type { + BACKGROUND_NONE = 0, + BACKGROUND_COLOR, + BACKGROUND_TILES, }; enum blend_subs_mode { @@ -155,9 +154,9 @@ struct gl_video_opts { int temporal_dither_period; char *error_diffusion; char *fbo_format; - int alpha_mode; + int background; bool use_rectangle; - struct m_color background; + struct m_color background_color; bool interpolation; float interpolation_threshold; int blend_subs; @@ -195,7 +194,7 @@ void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd); 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_render_frame(struct gl_video *p, struct vo_frame *frame, - struct ra_fbo fbo, int flags); + const struct ra_fbo *fbo, int flags); void gl_video_resize(struct gl_video *p, struct mp_rect *src, struct mp_rect *dst, struct mp_osd_res *osd); @@ -215,7 +214,6 @@ void gl_video_set_ambient_lux(struct gl_video *p, int lux); void gl_video_set_icc_profile(struct gl_video *p, bstr icc_data); bool gl_video_icc_auto_enabled(struct gl_video *p); bool gl_video_gamma_auto_enabled(struct gl_video *p); -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); @@ -234,5 +232,6 @@ void gl_video_configure_queue(struct gl_video *p, struct vo *vo); struct mp_image *gl_video_get_image(struct gl_video *p, int imgfmt, int w, int h, int stride_align, int flags); +struct mp_image_params *gl_video_get_target_params_ptr(struct gl_video *p); #endif diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c index 6c0e8a8..e202818 100644 --- a/video/out/gpu/video_shaders.c +++ b/video/out/gpu/video_shaders.c @@ -17,6 +17,8 @@ #include +#include + #include "video_shaders.h" #include "video.h" @@ -252,7 +254,7 @@ void pass_compute_polar(struct gl_shader_cache *sc, struct scaler *scaler, static void bicubic_calcweights(struct gl_shader_cache *sc, const char *t, const char *s) { // Explanation of how bicubic scaling with only 4 texel fetches is done: - // http://www.mate.tue.nl/mate/pdfs/10318.pdf + // // 'Efficient GPU-Based Texture Interpolation using Uniform B-Splines' // Explanation why this algorithm normally always blurs, even with unit // scaling: @@ -337,10 +339,10 @@ static const float SLOG_A = 0.432699, // // These functions always output to a normalized scale of [0,1], for // convenience of the video.c code that calls it. To get the values in an -// absolute scale, multiply the result by `mp_trc_nom_peak(trc)` -void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) +// absolute scale, multiply the result by `pl_color_transfer_nominal_peak(trc)` +void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) { - if (trc == MP_CSP_TRC_LINEAR) + if (trc == PL_COLOR_TRC_LINEAR) return; GLSLF("// linearize\n"); @@ -353,40 +355,40 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) switch (trc) { - case MP_CSP_TRC_SRGB: + case PL_COLOR_TRC_SRGB: GLSLF("color.rgb = mix(color.rgb * vec3(1.0/12.92), \n" " pow((color.rgb + vec3(0.055))/vec3(1.055), vec3(2.4)), \n" " %s(lessThan(vec3(0.04045), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_BT_1886: + case PL_COLOR_TRC_BT_1886: GLSL(color.rgb = pow(color.rgb, vec3(2.4));) break; - case MP_CSP_TRC_GAMMA18: + case PL_COLOR_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.8));) break; - case MP_CSP_TRC_GAMMA20: + case PL_COLOR_TRC_GAMMA20: GLSL(color.rgb = pow(color.rgb, vec3(2.0));) break; - case MP_CSP_TRC_GAMMA22: + case PL_COLOR_TRC_GAMMA22: GLSL(color.rgb = pow(color.rgb, vec3(2.2));) break; - case MP_CSP_TRC_GAMMA24: + case PL_COLOR_TRC_GAMMA24: GLSL(color.rgb = pow(color.rgb, vec3(2.4));) break; - case MP_CSP_TRC_GAMMA26: + case PL_COLOR_TRC_GAMMA26: GLSL(color.rgb = pow(color.rgb, vec3(2.6));) break; - case MP_CSP_TRC_GAMMA28: + case PL_COLOR_TRC_GAMMA28: GLSL(color.rgb = pow(color.rgb, vec3(2.8));) break; - case MP_CSP_TRC_PRO_PHOTO: + case PL_COLOR_TRC_PRO_PHOTO: GLSLF("color.rgb = mix(color.rgb * vec3(1.0/16.0), \n" " pow(color.rgb, vec3(1.8)), \n" " %s(lessThan(vec3(0.03125), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_PQ: + case PL_COLOR_TRC_PQ: GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", PQ_M2); GLSLF("color.rgb = max(color.rgb - vec3(%f), vec3(0.0)) \n" " / (vec3(%f) - vec3(%f) * color.rgb);\n", @@ -396,33 +398,33 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) // MP_REF_WHITE instead, so rescale GLSLF("color.rgb *= vec3(%f);\n", 10000 / MP_REF_WHITE); break; - case MP_CSP_TRC_HLG: + case PL_COLOR_TRC_HLG: GLSLF("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,\n" " exp((color.rgb - vec3(%f)) * vec3(1.0/%f)) + vec3(%f),\n" " %s(lessThan(vec3(0.5), color.rgb)));\n", HLG_C, HLG_A, HLG_B, gl_sc_bvec(sc, 3)); GLSLF("color.rgb *= vec3(1.0/%f);\n", MP_REF_WHITE_HLG); break; - case MP_CSP_TRC_V_LOG: + case PL_COLOR_TRC_V_LOG: GLSLF("color.rgb = mix((color.rgb - vec3(0.125)) * vec3(1.0/5.6), \n" " pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n" " - vec3(%f), \n" " %s(lessThanEqual(vec3(0.181), color.rgb))); \n", VLOG_D, VLOG_C, VLOG_B, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_S_LOG1: + case PL_COLOR_TRC_S_LOG1: GLSLF("color.rgb = pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f))\n" " - vec3(%f);\n", SLOG_C, SLOG_A, SLOG_B); break; - case MP_CSP_TRC_S_LOG2: + case PL_COLOR_TRC_S_LOG2: GLSLF("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/%f), \n" " (pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n" " - vec3(%f)) * vec3(1.0/%f), \n" " %s(lessThanEqual(vec3(%f), color.rgb))); \n", SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, gl_sc_bvec(sc, 3), SLOG_Q); break; - case MP_CSP_TRC_ST428: + case PL_COLOR_TRC_ST428: GLSL(color.rgb = vec3(52.37/48.0) * pow(color.rgb, vec3(2.6));); break; default: @@ -430,7 +432,7 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) } // Rescale to prevent clipping on non-float textures - GLSLF("color.rgb *= vec3(1.0/%f);\n", mp_trc_nom_peak(trc)); + GLSLF("color.rgb *= vec3(1.0/%f);\n", pl_color_transfer_nominal_peak(trc)); } // Delinearize (compress), given a TRC as output. This corresponds to the @@ -438,51 +440,51 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) // reference monitor. // // Like pass_linearize, this functions ingests values on an normalized scale -void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) +void pass_delinearize(struct gl_shader_cache *sc, enum pl_color_transfer trc) { - if (trc == MP_CSP_TRC_LINEAR) + if (trc == PL_COLOR_TRC_LINEAR) return; GLSLF("// delinearize\n"); GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) - GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(trc)); + GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(trc)); switch (trc) { - case MP_CSP_TRC_SRGB: + case PL_COLOR_TRC_SRGB: GLSLF("color.rgb = mix(color.rgb * vec3(12.92), \n" " vec3(1.055) * pow(color.rgb, vec3(1.0/2.4)) \n" " - vec3(0.055), \n" " %s(lessThanEqual(vec3(0.0031308), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_BT_1886: + case PL_COLOR_TRC_BT_1886: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) break; - case MP_CSP_TRC_GAMMA18: + case PL_COLOR_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.8));) break; - case MP_CSP_TRC_GAMMA20: + case PL_COLOR_TRC_GAMMA20: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.0));) break; - case MP_CSP_TRC_GAMMA22: + case PL_COLOR_TRC_GAMMA22: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.2));) break; - case MP_CSP_TRC_GAMMA24: + case PL_COLOR_TRC_GAMMA24: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) break; - case MP_CSP_TRC_GAMMA26: + case PL_COLOR_TRC_GAMMA26: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.6));) break; - case MP_CSP_TRC_GAMMA28: + case PL_COLOR_TRC_GAMMA28: GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.8));) break; - case MP_CSP_TRC_PRO_PHOTO: + case PL_COLOR_TRC_PRO_PHOTO: GLSLF("color.rgb = mix(color.rgb * vec3(16.0), \n" " pow(color.rgb, vec3(1.0/1.8)), \n" " %s(lessThanEqual(vec3(0.001953), color.rgb))); \n", gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_PQ: + case PL_COLOR_TRC_PQ: GLSLF("color.rgb *= vec3(1.0/%f);\n", 10000 / MP_REF_WHITE); GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", PQ_M1); GLSLF("color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n" @@ -490,32 +492,32 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) PQ_C1, PQ_C2, PQ_C3); GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", PQ_M2); break; - case MP_CSP_TRC_HLG: + case PL_COLOR_TRC_HLG: GLSLF("color.rgb *= vec3(%f);\n", MP_REF_WHITE_HLG); GLSLF("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),\n" " vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),\n" " %s(lessThan(vec3(1.0), color.rgb)));\n", HLG_A, HLG_B, HLG_C, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_V_LOG: + case PL_COLOR_TRC_V_LOG: GLSLF("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125), \n" " vec3(%f) * log(color.rgb + vec3(%f)) \n" " + vec3(%f), \n" " %s(lessThanEqual(vec3(0.01), color.rgb))); \n", VLOG_C / M_LN10, VLOG_B, VLOG_D, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_S_LOG1: + case PL_COLOR_TRC_S_LOG1: GLSLF("color.rgb = vec3(%f) * log(color.rgb + vec3(%f)) + vec3(%f);\n", SLOG_A / M_LN10, SLOG_B, SLOG_C); break; - case MP_CSP_TRC_S_LOG2: + case PL_COLOR_TRC_S_LOG2: GLSLF("color.rgb = mix(vec3(%f) * color.rgb + vec3(%f), \n" " vec3(%f) * log(vec3(%f) * color.rgb + vec3(%f)) \n" " + vec3(%f), \n" " %s(lessThanEqual(vec3(0.0), color.rgb))); \n", SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C, gl_sc_bvec(sc, 3)); break; - case MP_CSP_TRC_ST428: + case PL_COLOR_TRC_ST428: GLSL(color.rgb = pow(color.rgb * vec3(48.0/52.37), vec3(1.0/2.6));); break; default: @@ -834,42 +836,42 @@ static void pass_tone_map(struct gl_shader_cache *sc, // the caller to have already bound the appropriate SSBO and set up the compute // shader metadata void pass_color_map(struct gl_shader_cache *sc, bool is_linear, - struct mp_colorspace src, struct mp_colorspace dst, + struct pl_color_space src, struct pl_color_space dst, + enum mp_csp_light src_light, enum mp_csp_light dst_light, const struct gl_tone_map_opts *opts) { GLSLF("// color mapping\n"); // Some operations need access to the video's luma coefficients, so make // them available - float rgb2xyz[3][3]; - mp_get_rgb2xyz_matrix(mp_get_csp_primaries(src.primaries), rgb2xyz); - gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz[1]); - mp_get_rgb2xyz_matrix(mp_get_csp_primaries(dst.primaries), rgb2xyz); - gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz[1]); - - bool need_ootf = src.light != dst.light; - if (src.light == MP_CSP_LIGHT_SCENE_HLG && src.hdr.max_luma != dst.hdr.max_luma) + pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(src.primaries)); + gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz.m[1]); + rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(dst.primaries)); + gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz.m[1]); + + bool need_ootf = src_light != dst_light; + if (src_light == MP_CSP_LIGHT_SCENE_HLG && src.hdr.max_luma != dst.hdr.max_luma) need_ootf = true; // All operations from here on require linear light as a starting point, - // so we linearize even if src.gamma == dst.gamma when one of the other + // so we linearize even if src.gamma == dst.transfer when one of the other // operations needs it - bool need_linear = src.gamma != dst.gamma || + bool need_linear = src.transfer != dst.transfer || src.primaries != dst.primaries || src.hdr.max_luma != dst.hdr.max_luma || need_ootf; if (need_linear && !is_linear) { // We also pull it up so that 1.0 is the reference white - pass_linearize(sc, src.gamma); + pass_linearize(sc, src.transfer); is_linear = true; } // Pre-scale the incoming values into an absolute scale - GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(src.gamma)); + GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(src.transfer)); if (need_ootf) - pass_ootf(sc, src.light, src.hdr.max_luma / MP_REF_WHITE); + pass_ootf(sc, src_light, src.hdr.max_luma / MP_REF_WHITE); // Tone map to prevent clipping due to excessive brightness if (src.hdr.max_luma > dst.hdr.max_luma) { @@ -879,11 +881,11 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, // Adapt to the right colorspace if necessary if (src.primaries != dst.primaries) { - struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries), - csp_dst = mp_get_csp_primaries(dst.primaries); - float m[3][3] = {{0}}; - mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m); - gl_sc_uniform_mat3(sc, "cms_matrix", true, &m[0][0]); + const struct pl_raw_primaries *csp_src = pl_raw_primaries_get(src.primaries), + *csp_dst = pl_raw_primaries_get(dst.primaries); + pl_matrix3x3 m = pl_get_color_mapping_matrix(csp_src, csp_dst, + PL_INTENT_RELATIVE_COLORIMETRIC); + gl_sc_uniform_mat3(sc, "cms_matrix", true, &m.m[0][0]); GLSL(color.rgb = cms_matrix * color.rgb;) if (!opts->gamut_mode || opts->gamut_mode == GAMUT_DESATURATE) { @@ -900,14 +902,14 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, } if (need_ootf) - pass_inverse_ootf(sc, dst.light, dst.hdr.max_luma / MP_REF_WHITE); + pass_inverse_ootf(sc, dst_light, dst.hdr.max_luma / MP_REF_WHITE); // Post-scale the outgoing values from absolute scale to normalized. // For SDR, we normalize to the chosen signal peak. For HDR, we normalize // to the encoding range of the transfer function. float dst_range = dst.hdr.max_luma / MP_REF_WHITE; - if (mp_trc_is_hdr(dst.gamma)) - dst_range = mp_trc_nom_peak(dst.gamma); + if (pl_color_space_is_hdr(&dst)) + dst_range = pl_color_transfer_nominal_peak(dst.transfer); GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range); @@ -919,7 +921,7 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear, } if (is_linear) - pass_delinearize(sc, dst.gamma); + pass_delinearize(sc, dst.transfer); } // Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post. @@ -964,7 +966,7 @@ const struct m_sub_options deband_conf = { // Stochastically sample a debanded result from a hooked texture. void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, - AVLFG *lfg, enum mp_csp_trc trc) + AVLFG *lfg, enum pl_color_transfer trc) { // Initialize the PRNG GLSLF("{\n"); @@ -1008,7 +1010,7 @@ void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, GLSL(noise.z = rand(h); h = permute(h);) // Noise is scaled to the signal level to prevent extreme noise for HDR - float gain = opts->grain/8192.0 / mp_trc_nom_peak(trc); + float gain = opts->grain/8192.0 / pl_color_transfer_nominal_peak(trc); GLSLF("color.xyz += %f * (noise - vec3(0.5));\n", gain); GLSLF("}\n"); } diff --git a/video/out/gpu/video_shaders.h b/video/out/gpu/video_shaders.h index 27e7874..7547df6 100644 --- a/video/out/gpu/video_shaders.h +++ b/video/out/gpu/video_shaders.h @@ -44,15 +44,16 @@ void pass_sample_bicubic_fast(struct gl_shader_cache *sc); void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler, int w, int h); -void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); -void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); +void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc); +void pass_delinearize(struct gl_shader_cache *sc, enum pl_color_transfer trc); void pass_color_map(struct gl_shader_cache *sc, bool is_linear, - struct mp_colorspace src, struct mp_colorspace dst, + struct pl_color_space src, struct pl_color_space dst, + enum mp_csp_light src_light, enum mp_csp_light dst_light, const struct gl_tone_map_opts *opts); void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, - AVLFG *lfg, enum mp_csp_trc trc); + AVLFG *lfg, enum pl_color_transfer trc); void pass_sample_unsharp(struct gl_shader_cache *sc, float param); diff --git a/video/out/gpu_next/context.c b/video/out/gpu_next/context.c index 2887cff..2c7c9fa 100644 --- a/video/out/gpu_next/context.c +++ b/video/out/gpu_next/context.c @@ -88,6 +88,7 @@ static bool d3d11_pl_init(struct vo *vo, struct gpu_ctx *ctx, ctx->swapchain = pl_d3d11_create_swapchain(d3d11, pl_d3d11_swapchain_params( .swapchain = swapchain, + .disable_10bit_sdr = ra_d3d11_ctx_prefer_8bit_output_format(ctx->ra_ctx), ) ); if (!ctx->swapchain) { @@ -106,13 +107,10 @@ err_out: } #endif // HAVE_D3D11 -struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct gl_video_opts *gl_opts) +struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct ra_ctx_opts *ctx_opts) { struct gpu_ctx *ctx = talloc_zero(NULL, struct gpu_ctx); ctx->log = vo->log; - - struct ra_ctx_opts *ctx_opts = mp_get_config_group(ctx, vo->global, &ra_ctx_conf); - ctx_opts->want_alpha = gl_opts->alpha_mode == ALPHA_YES; ctx->ra_ctx = ra_ctx_create(vo, *ctx_opts); if (!ctx->ra_ctx) goto err_out; @@ -145,18 +143,17 @@ struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct gl_video_opts *gl_opts) #if HAVE_GL && defined(PL_HAVE_OPENGL) if (ra_is_gl(ctx->ra_ctx->ra)) { struct GL *gl = ra_gl_get(ctx->ra_ctx->ra); - pl_opengl opengl = pl_opengl_create(ctx->pllog, - pl_opengl_params( - .debug = ctx_opts->debug, - .allow_software = ctx_opts->allow_sw, - .get_proc_addr_ex = (void *) gl->get_fn, - .proc_ctx = gl->fn_ctx, + struct pl_opengl_params params = *pl_opengl_params( + .debug = ctx_opts->debug, + .allow_software = ctx_opts->allow_sw, + .get_proc_addr_ex = (void *) gl->get_fn, + .proc_ctx = gl->fn_ctx, + ); # if HAVE_EGL - .egl_display = eglGetCurrentDisplay(), - .egl_context = eglGetCurrentContext(), + params.egl_display = eglGetCurrentDisplay(); + params.egl_context = eglGetCurrentContext(); # endif - ) - ); + pl_opengl opengl = pl_opengl_create(ctx->pllog, ¶ms); if (!opengl) goto err_out; ctx->gpu = opengl->gpu; diff --git a/video/out/gpu_next/context.h b/video/out/gpu_next/context.h index b98b9e7..aa44196 100644 --- a/video/out/gpu_next/context.h +++ b/video/out/gpu_next/context.h @@ -21,8 +21,8 @@ struct mp_log; struct ra_ctx; +struct ra_ctx_opts; struct vo; -struct gl_video_opts; struct gpu_ctx { struct mp_log *log; @@ -35,6 +35,6 @@ struct gpu_ctx { void *priv; }; -struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct gl_video_opts *gl_opts); +struct gpu_ctx *gpu_ctx_create(struct vo *vo, struct ra_ctx_opts *ctx_opts); bool gpu_ctx_resize(struct gpu_ctx *ctx, int w, int h); void gpu_ctx_destroy(struct gpu_ctx **ctxp); diff --git a/video/out/hwdec/dmabuf_interop.h b/video/out/hwdec/dmabuf_interop.h index e9b3e8e..3bf01a0 100644 --- a/video/out/hwdec/dmabuf_interop.h +++ b/video/out/hwdec/dmabuf_interop.h @@ -38,7 +38,7 @@ struct dmabuf_interop { struct dmabuf_interop_priv { int num_planes; struct mp_image layout; - struct ra_tex *tex[4]; + struct ra_tex *tex[AV_DRM_MAX_PLANES]; AVDRMFrameDescriptor desc; bool surface_acquired; diff --git a/video/out/hwdec/dmabuf_interop_gl.c b/video/out/hwdec/dmabuf_interop_gl.c index e7fb103..0f6fb89 100644 --- a/video/out/hwdec/dmabuf_interop_gl.c +++ b/video/out/hwdec/dmabuf_interop_gl.c @@ -52,39 +52,28 @@ typedef void *EGLImageKHR; #define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A struct vaapi_gl_mapper_priv { - GLuint gl_textures[4]; - EGLImageKHR images[4]; + GLuint gl_textures[AV_DRM_MAX_PLANES]; + EGLImageKHR images[AV_DRM_MAX_PLANES]; + + const struct ra_format *planes[AV_DRM_MAX_PLANES]; EGLImageKHR (EGLAPIENTRY *CreateImageKHR)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *); EGLBoolean (EGLAPIENTRY *DestroyImageKHR)(EGLDisplay, EGLImageKHR); void (EGLAPIENTRY *EGLImageTargetTexture2DOES)(GLenum, GLeglImageOES); + void (EGLAPIENTRY *EGLImageTargetTexStorageEXT)(GLenum, GLeglImageOES, + const GLint *); }; -static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, - const struct ra_imgfmt_desc *desc) +static bool gl_create_textures(struct ra_hwdec_mapper *mapper) { struct dmabuf_interop_priv *p_mapper = mapper->priv; - struct vaapi_gl_mapper_priv *p = talloc_ptrtype(NULL, p); - p_mapper->interop_mapper_priv = p; - - *p = (struct vaapi_gl_mapper_priv) { - // EGL_KHR_image_base - .CreateImageKHR = (void *)eglGetProcAddress("eglCreateImageKHR"), - .DestroyImageKHR = (void *)eglGetProcAddress("eglDestroyImageKHR"), - // GL_OES_EGL_image - .EGLImageTargetTexture2DOES = - (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES"), - }; - - if (!p->CreateImageKHR || !p->DestroyImageKHR || - !p->EGLImageTargetTexture2DOES) - return false; + struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; GL *gl = ra_gl_get(mapper->ra); - gl->GenTextures(4, p->gl_textures); - for (int n = 0; n < desc->num_planes; n++) { + gl->GenTextures(AV_DRM_MAX_PLANES, p->gl_textures); + for (int n = 0; n < p_mapper->num_planes; n++) { gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -97,7 +86,7 @@ static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, .w = mp_image_plane_w(&p_mapper->layout, n), .h = mp_image_plane_h(&p_mapper->layout, n), .d = 1, - .format = desc->planes[n], + .format = p->planes[n], .render_src = true, .src_linear = true, }; @@ -114,44 +103,99 @@ static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, return true; } +static void gl_delete_textures(const struct ra_hwdec_mapper *mapper) +{ + struct dmabuf_interop_priv *p_mapper = mapper->priv; + struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; + + GL *gl = ra_gl_get(mapper->ra); + gl->DeleteTextures(AV_DRM_MAX_PLANES, p->gl_textures); + for (int n = 0; n < AV_DRM_MAX_PLANES; n++) { + p->gl_textures[n] = 0; + ra_tex_free(mapper->ra, &p_mapper->tex[n]); + } +} + +static bool vaapi_gl_mapper_init(struct ra_hwdec_mapper *mapper, + const struct ra_imgfmt_desc *desc) +{ + struct dmabuf_interop_priv *p_mapper = mapper->priv; + struct vaapi_gl_mapper_priv *p = talloc_ptrtype(NULL, p); + p_mapper->interop_mapper_priv = p; + + *p = (struct vaapi_gl_mapper_priv) { + // EGL_KHR_image_base + .CreateImageKHR = (void *)eglGetProcAddress("eglCreateImageKHR"), + .DestroyImageKHR = (void *)eglGetProcAddress("eglDestroyImageKHR"), + }; + if (ra_gl_get(mapper->ra)->es) { + // GL_OES_EGL_image + p->EGLImageTargetTexture2DOES = + (void *)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + } else { + // GL_EXT_EGL_image_storage + p->EGLImageTargetTexStorageEXT = + (void *)eglGetProcAddress("glEGLImageTargetTexStorageEXT"); + } + + if (!p->CreateImageKHR || !p->DestroyImageKHR || + (!p->EGLImageTargetTexture2DOES && !p->EGLImageTargetTexStorageEXT)) { + return false; + } + + static_assert(MP_ARRAY_SIZE(desc->planes) == AV_DRM_MAX_PLANES, ""); + static_assert(MP_ARRAY_SIZE(mapper->tex) == AV_DRM_MAX_PLANES, ""); + + // remember format to allow texture recreation + for (int n = 0; n < desc->num_planes; n++) { + p->planes[n] = desc->planes[n]; + } + if (p->EGLImageTargetTexture2DOES) { + // created only once + if (!gl_create_textures(mapper)) + return false; + } + + return true; +} + static void vaapi_gl_mapper_uninit(const struct ra_hwdec_mapper *mapper) { struct dmabuf_interop_priv *p_mapper = mapper->priv; struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; if (p) { - GL *gl = ra_gl_get(mapper->ra); - gl->DeleteTextures(4, p->gl_textures); - for (int n = 0; n < 4; n++) { - p->gl_textures[n] = 0; - ra_tex_free(mapper->ra, &p_mapper->tex[n]); - } + gl_delete_textures(mapper); talloc_free(p); p_mapper->interop_mapper_priv = NULL; } } -#define ADD_ATTRIB(name, value) \ - do { \ - assert(num_attribs + 3 < MP_ARRAY_SIZE(attribs)); \ - attribs[num_attribs++] = (name); \ - attribs[num_attribs++] = (value); \ - attribs[num_attribs] = EGL_NONE; \ +#define ADD_ATTRIB(name, value) \ + do { \ + assert(num_attribs + 3 < MP_ARRAY_SIZE(attribs)); \ + attribs[num_attribs++] = (name); \ + attribs[num_attribs++] = (value); \ + attribs[num_attribs] = EGL_NONE; \ } while(0) -#define ADD_PLANE_ATTRIBS(plane) do { \ - uint64_t drm_format_modifier = p_mapper->desc.objects[p_mapper->desc.layers[i].planes[j].object_index].format_modifier; \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _FD_EXT, \ - p_mapper->desc.objects[p_mapper->desc.layers[i].planes[j].object_index].fd); \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _OFFSET_EXT, \ - p_mapper->desc.layers[i].planes[j].offset); \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _PITCH_EXT, \ - p_mapper->desc.layers[i].planes[j].pitch); \ - if (dmabuf_interop->use_modifiers && drm_format_modifier != DRM_FORMAT_MOD_INVALID) { \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _MODIFIER_LO_EXT, drm_format_modifier & 0xfffffffful); \ - ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _MODIFIER_HI_EXT, drm_format_modifier >> 32); \ - } \ - } while (0) +#define ADD_PLANE_ATTRIBS(nplane) \ + do { \ + const AVDRMPlaneDescriptor *plane = &p_mapper->desc.layers[i].planes[j]; \ + const AVDRMObjectDescriptor *object = \ + &p_mapper->desc.objects[plane->object_index]; \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _FD_EXT, object->fd); \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _OFFSET_EXT, plane->offset); \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _PITCH_EXT, plane->pitch); \ + uint64_t drm_format_modifier = object->format_modifier; \ + if (dmabuf_interop->use_modifiers && \ + drm_format_modifier != DRM_FORMAT_MOD_INVALID) { \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _MODIFIER_LO_EXT, \ + drm_format_modifier & 0xfffffffful); \ + ADD_ATTRIB(EGL_DMA_BUF_PLANE ## nplane ## _MODIFIER_HI_EXT, \ + drm_format_modifier >> 32); \ + } \ + } while (0) static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, struct dmabuf_interop *dmabuf_interop, @@ -162,6 +206,11 @@ static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, GL *gl = ra_gl_get(mapper->ra); + if (p->EGLImageTargetTexStorageEXT) { + if (!gl_create_textures(mapper)) + return false; + } + for (int i = 0, n = 0; i < p_mapper->desc.nb_layers; i++) { /* * As we must map surfaces as one texture per plane, we can only support @@ -186,6 +235,7 @@ static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, format[2] = DRM_FORMAT_R8; break; case DRM_FORMAT_P010: + case DRM_FORMAT_P210: #ifdef DRM_FORMAT_P030 /* Format added in a newer libdrm version than minimum */ case DRM_FORMAT_P030: #endif @@ -251,7 +301,11 @@ static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, } gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]); - p->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, p->images[n]); + if (p->EGLImageTargetTexStorageEXT) { + p->EGLImageTargetTexStorageEXT(GL_TEXTURE_2D, p->images[n], NULL); + } else { + p->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, p->images[n]); + } mapper->tex[n] = p_mapper->tex[n]; } @@ -266,12 +320,18 @@ static void vaapi_gl_unmap(struct ra_hwdec_mapper *mapper) struct dmabuf_interop_priv *p_mapper = mapper->priv; struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv; - if (p) { - for (int n = 0; n < 4; n++) { - if (p->images[n]) - p->DestroyImageKHR(eglGetCurrentDisplay(), p->images[n]); - p->images[n] = 0; - } + if (!p) + return; + + if (p->EGLImageTargetTexStorageEXT) { + // textures are immutable, can't reuse + gl_delete_textures(mapper); + } + + for (int n = 0; n < AV_DRM_MAX_PLANES; n++) { + if (p->images[n]) + p->DestroyImageKHR(eglGetCurrentDisplay(), p->images[n]); + p->images[n] = 0; } } @@ -291,16 +351,18 @@ bool dmabuf_interop_gl_init(const struct ra_hwdec *hw, return false; GL *gl = ra_gl_get(hw->ra_ctx->ra); + const char *imageext = gl->es ? "GL_OES_EGL_image" : "GL_EXT_EGL_image_storage"; if (!gl_check_extension(exts, "EGL_EXT_image_dma_buf_import") || !gl_check_extension(exts, "EGL_KHR_image_base") || - !gl_check_extension(gl->extensions, "GL_OES_EGL_image") || - !(gl->mpgl_caps & MPGL_CAP_TEX_RG)) + !gl_check_extension(gl->extensions, imageext) || + !(gl->mpgl_caps & MPGL_CAP_TEX_RG)) { return false; + } dmabuf_interop->use_modifiers = gl_check_extension(exts, "EGL_EXT_image_dma_buf_import_modifiers"); - MP_VERBOSE(hw, "using EGL dmabuf interop\n"); + MP_VERBOSE(hw, "Using EGL dmabuf interop via %s\n", imageext); dmabuf_interop->interop_init = vaapi_gl_mapper_init; dmabuf_interop->interop_uninit = vaapi_gl_mapper_uninit; diff --git a/video/out/hwdec/dmabuf_interop_pl.c b/video/out/hwdec/dmabuf_interop_pl.c index 0a8ec5b..1f036e3 100644 --- a/video/out/hwdec/dmabuf_interop_pl.c +++ b/video/out/hwdec/dmabuf_interop_pl.c @@ -110,7 +110,7 @@ static bool vaapi_pl_map(struct ra_hwdec_mapper *mapper, static void vaapi_pl_unmap(struct ra_hwdec_mapper *mapper) { - for (int n = 0; n < 4; n++) + for (int n = 0; n < MP_ARRAY_SIZE(mapper->tex); n++) ra_tex_free(mapper->ra, &mapper->tex[n]); } diff --git a/video/out/hwdec/hwdec_aimagereader.c b/video/out/hwdec/hwdec_aimagereader.c index 0dd5497..1aa92ee 100644 --- a/video/out/hwdec/hwdec_aimagereader.c +++ b/video/out/hwdec/hwdec_aimagereader.c @@ -75,7 +75,7 @@ struct priv { void (EGLAPIENTRY *EGLImageTargetTexture2DOES)(GLenum, GLeglImageOES); }; -const static struct { const char *symbol; int offset; } lib_functions[] = { +static const struct { const char *symbol; int offset; } lib_functions[] = { { "AImageReader_newWithUsage", offsetof(struct priv_owner, AImageReader_newWithUsage) }, { "AImageReader_getWindow", offsetof(struct priv_owner, AImageReader_getWindow) }, { "AImageReader_setImageListener", offsetof(struct priv_owner, AImageReader_setImageListener) }, @@ -138,6 +138,10 @@ static int init(struct ra_hwdec *hw) if (!gl_check_extension(exts, "EGL_ANDROID_image_native_buffer")) return -1; + JNIEnv *env = MP_JNI_GET_ENV(hw); + if (!env) + return -1; + if (!load_lib_functions(p, hw->log)) return -1; @@ -167,8 +171,6 @@ static int init(struct ra_hwdec *hw) } assert(window); - JNIEnv *env = MP_JNI_GET_ENV(hw); - assert(env); jobject surface = p->ANativeWindow_toSurface(env, window); p->surface = (*env)->NewGlobalRef(env, surface); (*env)->DeleteLocalRef(env, surface); @@ -192,10 +194,10 @@ static int init(struct ra_hwdec *hw) static void uninit(struct ra_hwdec *hw) { struct priv_owner *p = hw->priv; - JNIEnv *env = MP_JNI_GET_ENV(hw); - assert(env); if (p->surface) { + JNIEnv *env = MP_JNI_GET_ENV(hw); + assert(env); (*env)->DeleteGlobalRef(env, p->surface); p->surface = NULL; } diff --git a/video/out/hwdec/hwdec_cuda.c b/video/out/hwdec/hwdec_cuda.c index 68ad60d..57e4fb4 100644 --- a/video/out/hwdec/hwdec_cuda.c +++ b/video/out/hwdec/hwdec_cuda.c @@ -57,7 +57,7 @@ int check_cu(const struct ra_hwdec *hw, CUresult err, const char *func) #define CHECK_CU(x) check_cu(hw, (x), #x) -const static cuda_interop_init interop_inits[] = { +static const cuda_interop_init interop_inits[] = { #if HAVE_GL cuda_gl_init, #endif diff --git a/video/out/hwdec/hwdec_drmprime.c b/video/out/hwdec/hwdec_drmprime.c index f7c6250..bf60405 100644 --- a/video/out/hwdec/hwdec_drmprime.c +++ b/video/out/hwdec/hwdec_drmprime.c @@ -23,6 +23,7 @@ #include #include +#include #include #include "config.h" @@ -51,7 +52,7 @@ static void uninit(struct ra_hwdec *hw) av_buffer_unref(&p->hwctx.av_device_ref); } -const static dmabuf_interop_init interop_inits[] = { +static const dmabuf_interop_init interop_inits[] = { #if HAVE_DMABUF_INTEROP_GL dmabuf_interop_gl_init, #endif @@ -64,6 +65,18 @@ const static dmabuf_interop_init interop_inits[] = { NULL }; +/** + * Due to the fact that Raspberry Pi support only exists in forked ffmpegs and + * also requires custom pixel formats, we need some way to work with those formats + * without introducing any build time dependencies. We do this by looking up the + * pixel formats by name. As rpi is an important target platform for this hwdec + * we don't really have the luxury of ignoring these forks. + */ +static const char *forked_pix_fmt_names[] = { + "rpi4_8", + "rpi4_10", +}; + static int init(struct ra_hwdec *hw) { struct priv_owner *p = hw->priv; @@ -119,6 +132,18 @@ static int init(struct ra_hwdec *hw) MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_NV12); MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_420P); MP_TARRAY_APPEND(p, p->formats, num_formats, pixfmt2imgfmt(AV_PIX_FMT_NV16)); + MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_P010); +#ifdef AV_PIX_FMT_P210 + MP_TARRAY_APPEND(p, p->formats, num_formats, pixfmt2imgfmt(AV_PIX_FMT_P210)); +#endif + + for (int i = 0; i < MP_ARRAY_SIZE(forked_pix_fmt_names); i++) { + enum AVPixelFormat fmt = av_get_pix_fmt(forked_pix_fmt_names[i]); + if (fmt != AV_PIX_FMT_NONE) { + MP_TARRAY_APPEND(p, p->formats, num_formats, pixfmt2imgfmt(fmt)); + } + } + MP_TARRAY_APPEND(p, p->formats, num_formats, 0); // terminate it p->hwctx.hw_imgfmt = IMGFMT_DRMPRIME; diff --git a/video/out/hwdec/hwdec_vaapi.c b/video/out/hwdec/hwdec_vaapi.c index d8a4517..34b6e52 100644 --- a/video/out/hwdec/hwdec_vaapi.c +++ b/video/out/hwdec/hwdec_vaapi.c @@ -124,7 +124,7 @@ static void uninit(struct ra_hwdec *hw) va_destroy(p->ctx); } -const static dmabuf_interop_init interop_inits[] = { +static const dmabuf_interop_init interop_inits[] = { #if HAVE_DMABUF_INTEROP_GL dmabuf_interop_gl_init, #endif @@ -261,10 +261,10 @@ static int mapper_init(struct ra_hwdec_mapper *mapper) return 0; } -static void close_file_descriptors(VADRMPRIMESurfaceDescriptor desc) +static void close_file_descriptors(const VADRMPRIMESurfaceDescriptor *desc) { - for (int i = 0; i < desc.num_objects; i++) - close(desc.objects[i].fd); + for (int i = 0; i < desc->num_objects; i++) + close(desc->objects[i].fd); } static int mapper_map(struct ra_hwdec_mapper *mapper) @@ -285,7 +285,7 @@ static int mapper_map(struct ra_hwdec_mapper *mapper) if (!CHECK_VA_STATUS_LEVEL(mapper, "vaExportSurfaceHandle()", p_owner->probing_formats ? MSGL_DEBUG : MSGL_ERR)) { - close_file_descriptors(desc); + close_file_descriptors(&desc); goto err; } vaSyncSurface(display, va_surface_id(mapper->src)); diff --git a/video/out/hwdec/hwdec_vt.c b/video/out/hwdec/hwdec_vt.c index ab41d02..643ff90 100644 --- a/video/out/hwdec/hwdec_vt.c +++ b/video/out/hwdec/hwdec_vt.c @@ -36,7 +36,7 @@ static void uninit(struct ra_hwdec *hw) av_buffer_unref(&p->hwctx.av_device_ref); } -const static vt_interop_init interop_inits[] = { +static const vt_interop_init interop_inits[] = { #if HAVE_VIDEOTOOLBOX_GL || HAVE_IOS_GL vt_gl_init, #endif diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift index aac7050..594a4b8 100644 --- a/video/out/mac/common.swift +++ b/video/out/mac/common.swift @@ -19,11 +19,13 @@ import Cocoa import IOKit.pwr_mgt class Common: NSObject { - var mpv: MPVHelper? + var option: OptionHelper + var input: InputHelper? var log: LogHelper + var vo: UnsafeMutablePointer? let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue") - var window: Window? + @objc var window: Window? var view: View? var titleBar: TitleBar? @@ -46,39 +48,26 @@ class Common: NSObject { didSet { if let window = window { window.title = title } } } - init(_ mpLog: OpaquePointer?) { - log = LogHelper(mpLog) + init(_ option: OptionHelper, _ log: LogHelper) { + self.option = option + self.log = log } func initMisc(_ vo: UnsafeMutablePointer) { - guard let mpv = mpv else { - log.sendError("Something went wrong, no MPVHelper was initialized") - exit(1) - } - startDisplayLink(vo) initLightSensor() addDisplayReconfigureObserver() addAppNotifications() - mpv.setMacOptionCallback(macOptsWakeupCallback, context: self) + option.setMacOptionCallback(macOptsWakeupCallback, context: self) } func initApp() { - guard let mpv = mpv else { - log.sendError("Something went wrong, no MPVHelper was initialized") - exit(1) - } - var policy: NSApplication.ActivationPolicy = .regular - switch mpv.macOpts.macos_app_activation_policy { - case 0: - policy = .regular - case 1: - policy = .accessory - case 2: - policy = .prohibited - default: - break + switch option.mac.macos_app_activation_policy { + case 0: policy = .regular + case 1: policy = .accessory + case 2: policy = .prohibited + default: break } NSApp.setActivationPolicy(policy) @@ -86,63 +75,67 @@ class Common: NSObject { } func initWindow(_ vo: UnsafeMutablePointer, _ previousActiveApp: NSRunningApplication?) { - let (mpv, targetScreen, wr) = getInitProperties(vo) + let (targetScreen, wr) = getInitProperties(vo) guard let view = self.view else { - log.sendError("Something went wrong, no View was initialized") + log.error("Something went wrong, no View was initialized") exit(1) } window = Window(contentRect: wr, screen: targetScreen, view: view, common: self) guard let window = self.window else { - log.sendError("Something went wrong, no Window was initialized") + log.error("Something went wrong, no Window was initialized") exit(1) } - window.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) - window.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces)) - window.keepAspect = Bool(mpv.opts.keepaspect_window) + window.setOnTop(Bool(option.vo.ontop), Int(option.vo.ontop_level)) + window.setOnAllWorkspaces(Bool(option.vo.all_workspaces)) + window.keepAspect = Bool(option.vo.keepaspect_window) window.title = title - window.border = Bool(mpv.opts.border) + window.border = Bool(option.vo.border) titleBar = TitleBar(frame: wr, window: window, common: self) - let minimized = Bool(mpv.opts.window_minimized) + let maximized = Bool(option.vo.window_maximized) + let minimized = Bool(option.vo.window_minimized) window.isRestorable = false window.isReleasedWhenClosed = false - window.setMaximized(minimized ? false : Bool(mpv.opts.window_maximized)) + window.setMaximized((minimized || !maximized) ? window.isZoomed : maximized) window.setMinimized(minimized) window.makeMain() window.makeKey() + view.layer?.contentsScale = window.backingScaleFactor + if !minimized { window.orderFront(nil) } - NSApp.activate(ignoringOtherApps: mpv.opts.focus_on_open) + NSApp.activate(ignoringOtherApps: option.vo.focus_on >= 1) // workaround for macOS 10.15 to refocus the previous App - if (!mpv.opts.focus_on_open) { - previousActiveApp?.activate(options: .activateAllWindows) + if option.vo.focus_on == 0 { + previousActiveApp?.activate() } } func initView(_ vo: UnsafeMutablePointer, _ layer: CALayer) { - let (_, _, wr) = getInitProperties(vo) + let (_, wr) = getInitProperties(vo) view = View(frame: wr, common: self) guard let view = self.view else { - log.sendError("Something went wrong, no View was initialized") + log.error("Something went wrong, no View was initialized") exit(1) } view.layer = layer view.wantsLayer = true view.layerContentsPlacement = .scaleProportionallyToFit + layer.delegate = view } func initWindowState() { - if mpv?.opts.fullscreen ?? false { + if option.vo.fullscreen { DispatchQueue.main.async { self.window?.toggleFullScreen(nil) } @@ -179,7 +172,7 @@ class Common: NSObject { guard let screen = getTargetScreen(forFullscreen: false) ?? NSScreen.main, let link = self.link else { - log.sendWarning("Couldn't start DisplayLink, no MPVHelper, Screen or DisplayLink available") + log.warning("Couldn't start DisplayLink, no Screen or DisplayLink available") return } @@ -198,7 +191,7 @@ class Common: NSObject { func updateDisplaylink() { guard let screen = window?.screen, let link = self.link else { - log.sendWarning("Couldn't update DisplayLink, no Screen or DisplayLink available") + log.warning("Couldn't update DisplayLink, no Screen or DisplayLink available") return } @@ -221,17 +214,17 @@ class Common: NSObject { } if fabs(actualFps - nominalFps) > 0.1 { - log.sendVerbose("Falling back to nominal display refresh rate: \(nominalFps)") + log.verbose("Falling back to nominal display refresh rate: \(nominalFps)") return nominalFps } else { return actualFps } } } else { - log.sendWarning("No DisplayLink available") + log.warning("No DisplayLink available") } - log.sendWarning("Falling back to standard display refresh rate: 60Hz") + log.warning("Falling back to standard display refresh rate: 60Hz") return 60.0 } @@ -285,28 +278,28 @@ class Common: NSObject { } func lightSensorUpdate() { - log.sendWarning("lightSensorUpdate not implemented") + log.warning("lightSensorUpdate not implemented") } func initLightSensor() { let srv = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController")) if srv == IO_OBJECT_NULL { - log.sendVerbose("Can't find an ambient light sensor") + log.verbose("Can't find an ambient light sensor") return } lightSensorIOPort = IONotificationPortCreate(kIOMasterPortDefault) IONotificationPortSetDispatchQueue(lightSensorIOPort, queue) var n = io_object_t() - IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback, MPVHelper.bridge(obj: self), &n) + IOServiceAddInterestNotification(lightSensorIOPort, srv, kIOGeneralInterest, lightSensorCallback, TypeHelper.bridge(obj: self), &n) let kr = IOServiceOpen(srv, mach_task_self_, 0, &lightSensor) IOObjectRelease(srv) if kr != KERN_SUCCESS { - log.sendVerbose("Can't start ambient light sensor connection") + log.verbose("Can't start ambient light sensor connection") return } - lightSensorCallback(MPVHelper.bridge(obj: self), 0, 0, nil) + lightSensorCallback(TypeHelper.bridge(obj: self), 0, 0, nil) } func uninitLightSensor() { @@ -322,18 +315,18 @@ class Common: NSObject { let displayID = com.window?.screen?.displayID ?? display if displayID == display { - com.log.sendVerbose("Detected display mode change, updating screen refresh rate") + com.log.verbose("Detected display mode change, updating screen refresh rate") com.flagEvents(VO_EVENT_WIN_STATE) } } } func addDisplayReconfigureObserver() { - CGDisplayRegisterReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self)) + CGDisplayRegisterReconfigurationCallback(reconfigureCallback, TypeHelper.bridge(obj: self)) } func removeDisplayReconfigureObserver() { - CGDisplayRemoveReconfigurationCallback(reconfigureCallback, MPVHelper.bridge(obj: self)) + CGDisplayRemoveReconfigurationCallback(reconfigureCallback, TypeHelper.bridge(obj: self)) } func addAppNotifications() { @@ -365,10 +358,8 @@ class Common: NSObject { } func setAppIcon() { - if let app = NSApp as? Application, - ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" - { - NSApp.applicationIconImage = app.getMPVIcon() + if ProcessInfo.processInfo.environment["MPVBUNDLE"] != "true" { + NSApp.applicationIconImage = AppHub.shared.getIcon() } } @@ -381,12 +372,12 @@ class Common: NSObject { } func updateICCProfile() { - log.sendWarning("updateICCProfile not implemented") + log.warning("updateICCProfile not implemented") } func getScreenBy(id screenID: Int) -> NSScreen? { if screenID >= NSScreen.screens.count { - log.sendInfo("Screen ID \(screenID) does not exist, falling back to current device") + log.info("Screen ID \(screenID) does not exist, falling back to current device") return nil } else if screenID < 0 { return nil @@ -404,14 +395,9 @@ class Common: NSObject { } func getTargetScreen(forFullscreen fs: Bool) -> NSScreen? { - guard let mpv = mpv else { - log.sendWarning("Unexpected nil value in getTargetScreen") - return nil - } - - let screenID = fs ? mpv.opts.fsscreen_id : mpv.opts.screen_id + let screenID = fs ? option.vo.fsscreen_id : option.vo.screen_id var name: String? - if let screenName = fs ? mpv.opts.fsscreen_name : mpv.opts.screen_name { + if let screenName = fs ? option.vo.fsscreen_name : option.vo.screen_name { name = String(cString: screenName) } return getScreenBy(id: Int(screenID)) ?? getScreenBy(name: name) @@ -426,7 +412,7 @@ class Common: NSObject { func getWindowGeometry(forScreen screen: NSScreen, videoOut vo: UnsafeMutablePointer) -> NSRect { let r = screen.convertRectToBacking(screen.frame) - let targetFrame = (mpv?.macOpts.macos_geometry_calculation ?? Int32(FRAME_VISIBLE)) == FRAME_VISIBLE + let targetFrame = option.mac.macos_geometry_calculation == FRAME_VISIBLE ? screen.visibleFrame : screen.frame let rv = screen.convertRectToBacking(targetFrame) @@ -453,19 +439,15 @@ class Common: NSObject { return screen.convertRectFromBacking(NSMakeRect(x, y, width, height)) } - func getInitProperties(_ vo: UnsafeMutablePointer) -> (MPVHelper, NSScreen, NSRect) { - guard let mpv = mpv else { - log.sendError("Something went wrong, no MPVHelper was initialized") - exit(1) - } + func getInitProperties(_ vo: UnsafeMutablePointer) -> (NSScreen, NSRect) { guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else { - log.sendError("Something went wrong, no Screen was found") + log.error("Something went wrong, no Screen was found") exit(1) } let wr = getWindowGeometry(forScreen: targetScreen, videoOut: vo) - return (mpv, targetScreen, wr) + return (targetScreen, wr) } // call before initApp, because on macOS +10.15 it changes the active App @@ -478,11 +460,11 @@ class Common: NSObject { events |= ev eventsLock.unlock() - guard let vout = mpv?.vo else { - log.sendWarning("vo nil in flagEvents") + guard let vo = vo else { + log.warning("vo nil in flagEvents") return } - vo_wakeup(vout) + vo_wakeup(vo) } func checkEvents() -> Int { @@ -510,47 +492,54 @@ class Common: NSObject { request: UInt32, data: UnsafeMutableRawPointer?) -> Int32 { - guard let mpv = mpv else { - log.sendWarning("Unexpected nil value in Control Callback") - return VO_FALSE - } - switch mp_voctrl(request) { case VOCTRL_CHECK_EVENTS: events.pointee |= Int32(checkEvents()) return VO_TRUE case VOCTRL_VO_OPTS_CHANGED: var opt: UnsafeMutableRawPointer? - while mpv.nextChangedOption(property: &opt) { + while option.nextChangedOption(property: &opt) { switch opt { - case MPVHelper.getPointer(&mpv.optsPtr.pointee.border): + case TypeHelper.toPointer(&option.voPtr.pointee.border): DispatchQueue.main.async { - self.window?.border = Bool(mpv.opts.border) + self.window?.border = Bool(self.option.vo.border) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.fullscreen): + case TypeHelper.toPointer(&option.voPtr.pointee.fullscreen): DispatchQueue.main.async { self.window?.toggleFullScreen(nil) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.ontop): fallthrough - case MPVHelper.getPointer(&mpv.optsPtr.pointee.ontop_level): + case TypeHelper.toPointer(&option.voPtr.pointee.ontop): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.ontop_level): + DispatchQueue.main.async { + self.window?.setOnTop(Bool(self.option.vo.ontop), Int(self.option.vo.ontop_level)) + } + case TypeHelper.toPointer(&option.voPtr.pointee.all_workspaces): DispatchQueue.main.async { - self.window?.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) + self.window?.setOnAllWorkspaces(Bool(self.option.vo.all_workspaces)) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.all_workspaces): + case TypeHelper.toPointer(&option.voPtr.pointee.keepaspect_window): DispatchQueue.main.async { - self.window?.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces)) + self.window?.keepAspect = Bool(self.option.vo.keepaspect_window) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.keepaspect_window): + case TypeHelper.toPointer(&option.voPtr.pointee.window_minimized): DispatchQueue.main.async { - self.window?.keepAspect = Bool(mpv.opts.keepaspect_window) + self.window?.setMinimized(Bool(self.option.vo.window_minimized)) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.window_minimized): + case TypeHelper.toPointer(&option.voPtr.pointee.window_maximized): DispatchQueue.main.async { - self.window?.setMinimized(Bool(mpv.opts.window_minimized)) + self.window?.setMaximized(Bool(self.option.vo.window_maximized)) } - case MPVHelper.getPointer(&mpv.optsPtr.pointee.window_maximized): + case TypeHelper.toPointer(&option.voPtr.pointee.cursor_passthrough): DispatchQueue.main.async { - self.window?.setMaximized(Bool(mpv.opts.window_maximized)) + self.window?.ignoresMouseEvents = self.option.vo.cursor_passthrough + } + case TypeHelper.toPointer(&option.voPtr.pointee.geometry): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.autofit): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.autofit_smaller): fallthrough + case TypeHelper.toPointer(&option.voPtr.pointee.autofit_larger): + DispatchQueue.main.async { + let (_, wr) = self.getInitProperties(vo) + self.window?.updateFrame(wr) } default: break @@ -561,6 +550,13 @@ class Common: NSObject { let fps = data!.assumingMemoryBound(to: CDouble.self) fps.pointee = currentFps() return VO_TRUE + case VOCTRL_GET_WINDOW_ID: + guard let window = window else { + return VO_NOTAVAIL + } + let wid = data!.assumingMemoryBound(to: Int64.self) + wid.pointee = unsafeBitCast(window, to: Int64.self) + return VO_TRUE case VOCTRL_GET_HIDPI_SCALE: let scaleFactor = data!.assumingMemoryBound(to: CDouble.self) let screen = getCurrentScreen() @@ -584,7 +580,7 @@ class Common: NSObject { case VOCTRL_GET_ICC_PROFILE: let screen = getCurrentScreen() guard var iccData = screen?.colorSpace?.iccProfileData else { - log.sendWarning("No Screen available to retrieve ICC profile") + log.warning("No Screen available to retrieve ICC profile") return VO_TRUE } @@ -605,10 +601,8 @@ class Common: NSObject { case VOCTRL_GET_UNFS_WINDOW_SIZE: let sizeData = data!.assumingMemoryBound(to: Int32.self) let size = UnsafeMutableBufferPointer(start: sizeData, count: 2) - var rect = window?.unfsContentFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 720) - if let screen = window?.currentScreen, !Bool(mpv.opts.hidpi_window_scale) { - rect = screen.convertRectToBacking(rect) - } + let rect = (Bool(option.vo.hidpi_window_scale) ? window?.unfsContentFrame + : window?.unfsContentFramePixel) ?? NSRect(x: 0, y: 0, width: 1280, height: 720) size[0] = Int32(rect.size.width) size[1] = Int32(rect.size.height) @@ -618,7 +612,7 @@ class Common: NSObject { let size = UnsafeBufferPointer(start: sizeData, count: 2) var rect = NSMakeRect(0, 0, CGFloat(size[0]), CGFloat(size[1])) DispatchQueue.main.async { - if let screen = self.window?.currentScreen, !Bool(self.mpv?.opts.hidpi_window_scale ?? true) { + if let screen = self.window?.currentScreen, !Bool(self.option.vo.hidpi_window_scale) { rect = screen.convertRectFromBacking(rect) } self.window?.updateSize(rect.size) @@ -630,13 +624,13 @@ class Common: NSObject { var count: Int32 = 0 let displayName = getCurrentScreen()?.localizedName ?? "Unknown" - SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, ta_xstrdup(nil, displayName)) - SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, nil) + app_bridge_tarray_append(nil, &array, &count, ta_xstrdup(nil, displayName)) + app_bridge_tarray_append(nil, &array, &count, nil) dnames.pointee = array return VO_TRUE case VOCTRL_GET_DISPLAY_RES: guard let screen = getCurrentScreen() else { - log.sendWarning("No Screen available to retrieve frame") + log.warning("No Screen available to retrieve frame") return VO_NOTAVAIL } let sizeData = data!.assumingMemoryBound(to: Int32.self) @@ -650,10 +644,9 @@ class Common: NSObject { focus.pointee = NSApp.isActive return VO_TRUE case VOCTRL_UPDATE_WINDOW_TITLE: - let titleData = data!.assumingMemoryBound(to: Int8.self) + let title = String(cString: data!.assumingMemoryBound(to: CChar.self)) DispatchQueue.main.async { - let title = NSString(utf8String: titleData) as String? - self.title = title ?? "Unknown Title" + self.title = title } return VO_TRUE default: @@ -669,20 +662,15 @@ class Common: NSObject { } func macOptsUpdate() { - guard let mpv = mpv else { - log.sendWarning("Unexpected nil value in mac opts update") - return - } - var opt: UnsafeMutableRawPointer? - while mpv.nextChangedMacOption(property: &opt) { + while option.nextChangedMacOption(property: &opt) { switch opt { - case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_appearance): - titleBar?.set(appearance: Int(mpv.macOpts.macos_title_bar_appearance)) - case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_material): - titleBar?.set(material: Int(mpv.macOpts.macos_title_bar_material)) - case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_color): - titleBar?.set(color: mpv.macOpts.macos_title_bar_color) + case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_appearance): + titleBar?.set(appearance: Int(option.mac.macos_title_bar_appearance)) + case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_material): + titleBar?.set(material: Int(option.mac.macos_title_bar_material)) + case TypeHelper.toPointer(&option.macPtr.pointee.macos_title_bar_color): + titleBar?.set(color: option.mac.macos_title_bar_color) default: break } diff --git a/video/out/mac/gl_layer.swift b/video/out/mac/gl_layer.swift index dd96af7..38320bc 100644 --- a/video/out/mac/gl_layer.swift +++ b/video/out/mac/gl_layer.swift @@ -82,8 +82,6 @@ class GLLayer: CAOpenGLLayer { enum Draw: Int { case normal = 1, atomic, atomicEnd } var draw: Draw = .normal - let queue: DispatchQueue = DispatchQueue(label: "io.mpv.queue.draw") - var needsICCUpdate: Bool = false { didSet { if needsICCUpdate == true { @@ -199,6 +197,14 @@ class GLLayer: CAOpenGLLayer { } } + func lockCglContext() { + CGLLockContext(cglContext) + } + + func unlockCglContext() { + CGLUnlockContext(cglContext) + } + override func copyCGLPixelFormat(forDisplayMask mask: UInt32) -> CGLPixelFormatObj { return cglPixelFormat } @@ -219,17 +225,19 @@ class GLLayer: CAOpenGLLayer { super.display() CATransaction.flush() if isUpdate && needsFlip { + lockCglContext() CGLSetCurrentContext(cglContext) if libmpv.isRenderUpdateFrame() { libmpv.drawRender(NSZeroSize, bufferDepth, cglContext, skip: true) } + unlockCglContext() } displayLock.unlock() } func update(force: Bool = false) { if force { forceDraw = true } - queue.async { + DispatchQueue.main.async { if self.forceDraw || !self.inLiveResize { self.needsFlip = true self.display() @@ -241,7 +249,7 @@ class GLLayer: CAOpenGLLayer { var pix: CGLPixelFormatObj? var depth: GLint = 8 var err: CGLError = CGLError(rawValue: 0) - let swRender = ccb.libmpv.macOpts.cocoa_cb_sw_renderer + let swRender = ccb.option.mac.cocoa_cb_sw_renderer if swRender != 1 { (pix, depth, err) = GLLayer.findPixelFormat(ccb) @@ -252,7 +260,7 @@ class GLLayer: CAOpenGLLayer { } guard let pixelFormat = pix, err == kCGLNoError else { - ccb.log.sendError("Couldn't create any CGL pixel format") + ccb.log.error("Couldn't create any CGL pixel format") exit(1) } @@ -269,12 +277,12 @@ class GLLayer: CAOpenGLLayer { glBase.insert(CGLPixelFormatAttribute(ver.rawValue), at: 1) var glFormat = [glBase] - if ccb.libmpv.macOpts.cocoa_cb_10bit_context { + if ccb.option.mac.cocoa_cb_10bit_context { glFormat += [glFormat10Bit] } glFormat += glFormatOptional - if !ccb.libmpv.macOpts.macos_force_dedicated_gpu { + if !ccb.option.mac.macos_force_dedicated_gpu { glFormat += [glFormatAutoGPU] } @@ -289,7 +297,7 @@ class GLLayer: CAOpenGLLayer { return attributeLookUp[value.rawValue] ?? String(value.rawValue) }) - ccb.log.sendVerbose("Created CGL pixel format with attributes: " + + ccb.log.verbose("Created CGL pixel format with attributes: " + "\(attArray.joined(separator: ", "))") return (pix, glFormat.contains(glFormat10Bit) ? 16 : 8, err) } @@ -297,11 +305,11 @@ class GLLayer: CAOpenGLLayer { } let errS = String(cString: CGLErrorString(err)) - ccb.log.sendWarning("Couldn't create a " + + ccb.log.warning("Couldn't create a " + "\(software ? "software" : "hardware accelerated") " + "CGL pixel format: \(errS) (\(err.rawValue))") - if software == false && ccb.libmpv.macOpts.cocoa_cb_sw_renderer == -1 { - ccb.log.sendWarning("Falling back to software renderer") + if software == false && ccb.option.mac.cocoa_cb_sw_renderer == -1 { + ccb.log.warning("Falling back to software renderer") } return (pix, 8, err) @@ -313,7 +321,7 @@ class GLLayer: CAOpenGLLayer { guard let cglContext = context, error == kCGLNoError else { let errS = String(cString: CGLErrorString(error)) - ccb.log.sendError("Couldn't create a CGLContext: " + errS) + ccb.log.error("Couldn't create a CGLContext: " + errS) exit(1) } diff --git a/video/out/mac/metal_layer.swift b/video/out/mac/metal_layer.swift index 7cea87c..7fc419a 100644 --- a/video/out/mac/metal_layer.swift +++ b/video/out/mac/metal_layer.swift @@ -16,10 +16,22 @@ */ import Cocoa +import QuartzCore class MetalLayer: CAMetalLayer { unowned var common: MacCommon + // workaround for a MoltenVK workaround that sets the drawableSize to 1x1 to forcefully complete + // the presentation, this causes flicker and the drawableSize possibly staying at 1x1 + override var drawableSize: CGSize { + get { return super.drawableSize } + set { + if Int(newValue.width) > 1 && Int(newValue.height) > 1 { + super.drawableSize = newValue + } + } + } + init(common com: MacCommon) { common = com super.init() diff --git a/video/out/mac/title_bar.swift b/video/out/mac/title_bar.swift index 764c1ff..b274100 100644 --- a/video/out/mac/title_bar.swift +++ b/video/out/mac/title_bar.swift @@ -19,7 +19,7 @@ import Cocoa class TitleBar: NSVisualEffectView { unowned var common: Common - var mpv: MPVHelper? { get { return common.mpv } } + var option: OptionHelper { get { return common.option } } var systemBar: NSView? { get { return common.window?.standardWindowButton(.closeButton)?.superview } @@ -64,9 +64,9 @@ class TitleBar: NSVisualEffectView { window.contentView?.addSubview(self, positioned: .above, relativeTo: nil) window.titlebarAppearsTransparent = true window.styleMask.insert(.fullSizeContentView) - set(appearance: Int(mpv?.macOpts.macos_title_bar_appearance ?? 0)) - set(material: Int(mpv?.macOpts.macos_title_bar_material ?? 0)) - set(color: mpv?.macOpts.macos_title_bar_color ?? "#00000000") + set(appearance: Int(option.mac.macos_title_bar_appearance)) + set(material: Int(option.mac.macos_title_bar_material)) + set(color: option.mac.macos_title_bar_color) } required init?(coder: NSCoder) { @@ -195,10 +195,6 @@ class TitleBar: NSVisualEffectView { default: return nil } - - - let style = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") - return appearanceFrom(string: style == nil ? "aqua" : "vibrantDark") } func materialFrom(string: String) -> NSVisualEffectView.Material { @@ -221,9 +217,7 @@ class TitleBar: NSVisualEffectView { case "15", "light": return .light case "16", "mediumLight": return .mediumLight case "17", "ultraDark": return .ultraDark - default: break + default: return .titlebar } - - return .titlebar } } diff --git a/video/out/mac/view.swift b/video/out/mac/view.swift index c4776c3..047a523 100644 --- a/video/out/mac/view.swift +++ b/video/out/mac/view.swift @@ -17,9 +17,9 @@ import Cocoa -class View: NSView { +class View: NSView, CALayerDelegate { unowned var common: Common - var mpv: MPVHelper? { get { return common.mpv } } + var input: InputHelper? { get { return common.input } } var tracker: NSTrackingArea? var hasMouseDown: Bool = false @@ -52,7 +52,7 @@ class View: NSView { addTrackingArea(tracker!) if containsMouseLocation() { - cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) + input?.put(key: SWIFT_KEY_MOUSE_LEAVE) } } @@ -77,30 +77,24 @@ class View: NSView { override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { let pb = sender.draggingPasteboard guard let types = pb.types else { return false } + var files: [String] = [] if types.contains(.fileURL) || types.contains(.URL) { - if let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL] { - let files = urls.map { $0.absoluteString } - EventsResponder.sharedInstance().handleFilesArray(files) - return true - } + guard let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL] else { return false } + files = urls.map { $0.absoluteString } } else if types.contains(.string) { guard let str = pb.string(forType: .string) else { return false } - var filesArray: [String] = [] - - for val in str.components(separatedBy: "\n") { - let url = val.trimmingCharacters(in: .whitespacesAndNewlines) + files = str.components(separatedBy: "\n").compactMap { + let url = $0.trimmingCharacters(in: .whitespacesAndNewlines) let path = (url as NSString).expandingTildeInPath - if isURL(url) { - filesArray.append(url) - } else if path.starts(with: "/") { - filesArray.append(path) - } + if isURL(url) { return url } + if path.starts(with: "/") { return path } + return nil } - EventsResponder.sharedInstance().handleFilesArray(filesArray) - return true } - return false + if files.isEmpty { return false } + input?.open(files: files) + return true } override func acceptsFirstMouse(for event: NSEvent?) -> Bool { @@ -116,94 +110,66 @@ class View: NSView { } override func mouseEntered(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_ENTER, 0) + if input?.mouseEnabled() ?? true { + input?.put(key: SWIFT_KEY_MOUSE_ENTER) } common.updateCursorVisibility() } override func mouseExited(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - cocoa_put_key_with_modifiers(SWIFT_KEY_MOUSE_LEAVE, 0) + if input?.mouseEnabled() ?? true { + input?.put(key: SWIFT_KEY_MOUSE_LEAVE) } common.titleBar?.hide() common.setCursorVisibility(true) } override func mouseMoved(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseMovement(event) - } + signalMouseMovement(event) common.titleBar?.show() } override func mouseDragged(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseMovement(event) - } + signalMouseMovement(event) } override func mouseDown(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseDown(event) - } + hasMouseDown = event.clickCount <= 1 + input?.processMouse(event: event) } override func mouseUp(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseUp(event) - } + hasMouseDown = false common.window?.isMoving = false + input?.processMouse(event: event) } override func rightMouseDown(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseDown(event) - } + hasMouseDown = event.clickCount <= 1 + input?.processMouse(event: event) } override func rightMouseUp(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseUp(event) - } + hasMouseDown = false + input?.processMouse(event: event) } override func otherMouseDown(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseDown(event) - } + hasMouseDown = event.clickCount <= 1 + input?.processMouse(event: event) } override func otherMouseUp(with event: NSEvent) { - if mpv?.mouseEnabled() ?? true { - signalMouseUp(event) - } + hasMouseDown = false + input?.processMouse(event: event) } override func magnify(with event: NSEvent) { - event.phase == .ended ? - common.windowDidEndLiveResize() : common.windowWillStartLiveResize() - + common.window?.isAnimating = event.phase != .ended + event.phase == .ended ? common.windowDidEndLiveResize() : common.windowWillStartLiveResize() common.window?.addWindowScale(Double(event.magnification)) } - func signalMouseDown(_ event: NSEvent) { - signalMouseEvent(event, MP_KEY_STATE_DOWN) - if event.clickCount > 1 { - signalMouseEvent(event, MP_KEY_STATE_UP) - } - } - - func signalMouseUp(_ event: NSEvent) { - signalMouseEvent(event, MP_KEY_STATE_UP) - } - - func signalMouseEvent(_ event: NSEvent, _ state: UInt32) { - hasMouseDown = state == MP_KEY_STATE_DOWN - let mpkey = getMpvButton(event) - cocoa_put_key_with_modifiers((mpkey | Int32(state)), Int32(event.modifierFlags.rawValue)) - } - func signalMouseMovement(_ event: NSEvent) { var point = convert(event.locationInWindow, from: nil) point = convertToBacking(point) @@ -211,46 +177,12 @@ class View: NSView { common.window?.updateMovableBackground(point) if !(common.window?.isMoving ?? false) { - mpv?.setMousePosition(point) - } - } - - func preciseScroll(_ event: NSEvent) { - var delta: Double - var cmd: Int32 - - if abs(event.deltaY) >= abs(event.deltaX) { - delta = Double(event.deltaY) * 0.1 - cmd = delta > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN - } else { - delta = Double(event.deltaX) * 0.1 - cmd = delta > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT + input?.setMouse(position: point) } - - mpv?.putAxis(cmd, delta: abs(delta)) } override func scrollWheel(with event: NSEvent) { - if !(mpv?.mouseEnabled() ?? true) { - return - } - - if event.hasPreciseScrollingDeltas { - preciseScroll(event) - } else { - let modifiers = event.modifierFlags - let deltaX = modifiers.contains(.shift) ? event.scrollingDeltaY : event.scrollingDeltaX - let deltaY = modifiers.contains(.shift) ? event.scrollingDeltaX : event.scrollingDeltaY - var mpkey: Int32 - - if abs(deltaY) >= abs(deltaX) { - mpkey = deltaY > 0 ? SWIFT_WHEEL_UP : SWIFT_WHEEL_DOWN - } else { - mpkey = deltaX > 0 ? SWIFT_WHEEL_LEFT : SWIFT_WHEEL_RIGHT - } - - cocoa_put_key_with_modifiers(mpkey, Int32(modifiers.rawValue)) - } + input?.processWheel(event: event) } func containsMouseLocation() -> Bool { @@ -282,16 +214,4 @@ class View: NSView { guard let window = common.window else { return false } return !hasMouseDown && containsMouseLocation() && window.isKeyWindow } - - func getMpvButton(_ event: NSEvent) -> Int32 { - let buttonNumber = event.buttonNumber - switch (buttonNumber) { - case 0: return SWIFT_MBTN_LEFT - case 1: return SWIFT_MBTN_RIGHT - case 2: return SWIFT_MBTN_MID - case 3: return SWIFT_MBTN_BACK - case 4: return SWIFT_MBTN_FORWARD - default: return SWIFT_MBTN9 + Int32(buttonNumber - 5) - } - } } diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift index 7b1a858..c5a711e 100644 --- a/video/out/mac/window.swift +++ b/video/out/mac/window.swift @@ -19,14 +19,15 @@ import Cocoa class Window: NSWindow, NSWindowDelegate { weak var common: Common! = nil - var mpv: MPVHelper? { get { return common.mpv } } + var option: OptionHelper { get { return common.option } } + var input: InputHelper? { get { return common.input } } var targetScreen: NSScreen? var previousScreen: NSScreen? var currentScreen: NSScreen? var unfScreen: NSScreen? - var unfsContentFrame: NSRect? + var unfsContentFrame: NSRect = NSRect(x: 0, y: 0, width: 160, height: 90) var isInFullscreen: Bool = false var isMoving: Bool = false var previousStyleMask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable] @@ -34,8 +35,8 @@ class Window: NSWindow, NSWindowDelegate { var isAnimating: Bool = false let animationLock: NSCondition = NSCondition() - var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame ?? NSRect(x: 0, y: 0, width: 160, height: 90)) } } - var framePixel: NSRect { get { return convertToBacking(frame) } } + var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame) } } + @objc var framePixel: NSRect { get { return convertToBacking(frame) } } var keepAspect: Bool = true { didSet { @@ -44,7 +45,7 @@ class Window: NSWindow, NSWindowDelegate { } if keepAspect { - contentAspectRatio = unfsContentFrame?.size ?? contentAspectRatio + contentAspectRatio = unfsContentFrame.size } else { resizeIncrements = NSSize(width: 1.0, height: 1.0) } @@ -91,7 +92,9 @@ class Window: NSWindow, NSWindowDelegate { title = com.title minSize = NSMakeSize(160, 90) collectionBehavior = .fullScreenPrimary + ignoresMouseEvents = option.vo.cursor_passthrough delegate = self + unfsContentFrame = contentRect if let cView = contentView { cView.addSubview(view) @@ -103,13 +106,11 @@ class Window: NSWindow, NSWindowDelegate { currentScreen = screen unfScreen = screen - if let app = NSApp as? Application { - app.menuBar.register(#selector(setHalfWindowSize), for: MPM_H_SIZE) - app.menuBar.register(#selector(setNormalWindowSize), for: MPM_N_SIZE) - app.menuBar.register(#selector(setDoubleWindowSize), for: MPM_D_SIZE) - app.menuBar.register(#selector(performMiniaturize(_:)), for: MPM_MINIMIZE) - app.menuBar.register(#selector(performZoom(_:)), for: MPM_ZOOM) - } + AppHub.shared.menu?.register(#selector(setHalfWindowSize), key: .itemHalfSize) + AppHub.shared.menu?.register(#selector(setNormalWindowSize), key: .itemNormalSize) + AppHub.shared.menu?.register(#selector(setDoubleWindowSize), key: .itemDoubleSize) + AppHub.shared.menu?.register(#selector(performMiniaturize(_:)), key: .itemMinimize) + AppHub.shared.menu?.register(#selector(performZoom(_:)), key: .itemZoom) } override func toggleFullScreen(_ sender: Any?) { @@ -141,7 +142,7 @@ class Window: NSWindow, NSWindowDelegate { setFrame(frame, display: true) } - if Bool(mpv?.opts.native_fs ?? true) { + if Bool(option.vo.native_fs) { super.toggleFullScreen(sender) } else { if !isInFullscreen { @@ -192,7 +193,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidEnterFullScreen(_ notification: Notification) { isInFullscreen = true - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) common.updateCursorVisibility() endAnimation(frame) common.titleBar?.show() @@ -201,7 +202,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidExitFullScreen(_ notification: Notification) { guard let tScreen = targetScreen else { return } isInFullscreen = false - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) endAnimation(calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen)) common.view?.layerContentsPlacement = .scaleProportionallyToFit } @@ -249,7 +250,7 @@ class Window: NSWindow, NSWindowDelegate { setFrame(targetFrame, display: true) endAnimation() isInFullscreen = true - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) common.windowSetToFullScreen() } @@ -268,7 +269,7 @@ class Window: NSWindow, NSWindowDelegate { setFrame(newFrame, display: true) endAnimation() isInFullscreen = false - mpv?.setOption(fullscreen: isInFullscreen) + option.setOption(fullscreen: isInFullscreen) common.windowSetToWindow() } @@ -281,7 +282,7 @@ class Window: NSWindow, NSWindowDelegate { } func getFsAnimationDuration(_ def: Double) -> Double { - let duration = mpv?.macOpts.macos_fs_animation_duration ?? -1 + let duration = option.mac.macos_fs_animation_duration if duration < 0 { return def } else { @@ -334,7 +335,7 @@ class Window: NSWindow, NSWindowDelegate { func updateMovableBackground(_ pos: NSPoint) { if !isInFullscreen { - isMovableByWindowBackground = mpv?.canBeDraggedAt(pos) ?? true + isMovableByWindowBackground = input?.draggable(at: pos) ?? true } else { isMovableByWindowBackground = false } @@ -342,35 +343,31 @@ class Window: NSWindow, NSWindowDelegate { func updateFrame(_ rect: NSRect) { if rect != frame { - let cRect = frameRect(forContentRect: rect) unfsContentFrame = rect - setFrame(cRect, display: true) - common.windowDidUpdateFrame() + if !isInFullscreen { + let cRect = frameRect(forContentRect: rect) + setFrame(cRect, display: true) + common.windowDidUpdateFrame() + } } } func updateSize(_ size: NSSize) { if let currentSize = contentView?.frame.size, size != currentSize { let newContentFrame = centeredContentSize(for: frame, size: size) - if !isInFullscreen { - updateFrame(newContentFrame) - } else { - unfsContentFrame = newContentFrame - } + updateFrame(newContentFrame) } } override func setFrame(_ frameRect: NSRect, display flag: Bool) { if frameRect.width < minSize.width || frameRect.height < minSize.height { - common.log.sendVerbose("tried to set too small window size: \(frameRect.size)") + common.log.verbose("tried to set too small window size: \(frameRect.size)") return } super.setFrame(frameRect, display: flag) - if let size = unfsContentFrame?.size, keepAspect { - contentAspectRatio = size - } + if keepAspect { contentAspectRatio = unfsContentFrame.size } } func centeredContentSize(for rect: NSRect, size sz: NSSize) -> NSRect { @@ -391,10 +388,9 @@ class Window: NSWindow, NSWindowDelegate { } func calculateWindowPosition(for tScreen: NSScreen, withoutBounds: Bool) -> NSRect { - guard let contentFrame = unfsContentFrame, let screen = unfScreen else { - return frame - } - var newFrame = frameRect(forContentRect: contentFrame) + guard let screen = unfScreen else { return frame } + + var newFrame = frameRect(forContentRect: unfsContentFrame) let targetFrame = tScreen.frame let targetVisibleFrame = tScreen.visibleFrame let unfsScreenFrame = screen.frame @@ -504,12 +500,12 @@ class Window: NSWindow, NSWindowDelegate { @objc func setDoubleWindowSize() { setWindowScale(2.0) } func setWindowScale(_ scale: Double) { - mpv?.command("set window-scale \(scale)") + input?.command("set window-scale \(scale)") } func addWindowScale(_ scale: Double) { if !isInFullscreen { - mpv?.command("add window-scale \(scale)") + input?.command("add current-window-scale \(scale)") } } @@ -542,7 +538,7 @@ class Window: NSWindow, NSWindowDelegate { func windowDidEndLiveResize(_ notification: Notification) { common.windowDidEndLiveResize() - mpv?.setOption(maximized: isZoomed) + option.setOption(maximized: isZoomed) if let contentViewFrame = contentView?.frame, !isAnimating && !isInFullscreen @@ -552,20 +548,23 @@ class Window: NSWindow, NSWindowDelegate { } func windowDidResize(_ notification: Notification) { + if let contentViewFrame = contentView?.frame, !isAnimating && !isInFullscreen && !inLiveResize { + unfsContentFrame = convertToScreen(contentViewFrame) + } common.windowDidResize() } func windowShouldClose(_ sender: NSWindow) -> Bool { - cocoa_put_key(MP_KEY_CLOSE_WIN) + input?.put(key: MP_KEY_CLOSE_WIN) return false } func windowDidMiniaturize(_ notification: Notification) { - mpv?.setOption(minimized: true) + option.setOption(minimized: true) } func windowDidDeminiaturize(_ notification: Notification) { - mpv?.setOption(minimized: false) + option.setOption(minimized: false) } func windowDidResignKey(_ notification: Notification) { @@ -588,6 +587,6 @@ class Window: NSWindow, NSWindowDelegate { } func windowDidMove(_ notification: Notification) { - mpv?.setOption(maximized: isZoomed) + option.setOption(maximized: isZoomed) } } diff --git a/video/out/mac_common.swift b/video/out/mac_common.swift index 349712b..f29815d 100644 --- a/video/out/mac_common.swift +++ b/video/out/mac_common.swift @@ -20,16 +20,18 @@ import Cocoa class MacCommon: Common { @objc var layer: MetalLayer? + var presentation: Presentation? var timer: PreciseTimer? var swapTime: UInt64 = 0 let swapLock: NSCondition = NSCondition() - var needsICCUpdate: Bool = false - @objc init(_ vo: UnsafeMutablePointer) { - let newlog = mp_log_new(vo, vo.pointee.log, "mac") - super.init(newlog) - mpv = MPVHelper(vo, log) + let log = LogHelper(mp_log_new(vo, vo.pointee.log, "mac")) + let option = OptionHelper(vo, vo.pointee.global) + super.init(option, log) + self.vo = vo + input = InputHelper(vo.pointee.input_ctx, option) + presentation = Presentation(common: self) timer = PreciseTimer(common: self) DispatchQueue.main.sync { @@ -39,16 +41,16 @@ class MacCommon: Common { } @objc func config(_ vo: UnsafeMutablePointer) -> Bool { - mpv?.vo = vo + self.vo = vo DispatchQueue.main.sync { let previousActiveApp = getActiveApp() initApp() - let (_, _, wr) = getInitProperties(vo) + let (_, wr) = getInitProperties(vo) guard let layer = self.layer else { - log.sendError("Something went wrong, no MetalLayer was initialized") + log.error("Something went wrong, no MetalLayer was initialized") exit(1) } @@ -58,12 +60,18 @@ class MacCommon: Common { initWindowState() } - if !NSEqualSizes(window?.unfsContentFramePixel.size ?? NSZeroSize, wr.size) { + if !NSEqualSizes(window?.unfsContentFramePixel.size ?? NSZeroSize, wr.size) && + option.vo.auto_window_resize + { window?.updateSize(wr.size) } + if option.vo.focus_on == 2 { + NSApp.activate(ignoringOtherApps: true) + } + windowDidResize() - needsICCUpdate = true + updateICCProfile() } return true @@ -83,7 +91,7 @@ class MacCommon: Common { } @objc func swapBuffer() { - if mpv?.macOpts.macos_render_timer ?? Int32(RENDER_TIMER_CALLBACK) != RENDER_TIMER_SYSTEM { + if option.mac.macos_render_timer > RENDER_TIMER_SYSTEM { swapLock.lock() while(swapTime < 1) { swapLock.wait() @@ -91,18 +99,16 @@ class MacCommon: Common { swapTime = 0 swapLock.unlock() } - - if needsICCUpdate { - needsICCUpdate = false - updateICCProfile() - } } - func updateRenderSize(_ size: NSSize) { - mpv?.vo.pointee.dwidth = Int32(size.width) - mpv?.vo.pointee.dheight = Int32(size.height) - flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE) - } + @objc func fillVsync(info: UnsafeMutablePointer) { + if option.mac.macos_render_timer != RENDER_TIMER_PRESENTATION_FEEDBACK { return } + + let next = presentation?.next() + info.pointee.vsync_duration = next?.duration ?? -1 + info.pointee.skipped_vsyncs = next?.skipped ?? -1 + info.pointee.last_queue_display_time = next?.time ?? -1 + } override func displayLinkCallback(_ displayLink: CVDisplayLink, _ inNow: UnsafePointer, @@ -110,7 +116,6 @@ class MacCommon: Common { _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer) -> CVReturn { - let frameTimer = mpv?.macOpts.macos_render_timer ?? Int32(RENDER_TIMER_CALLBACK) let signalSwap = { self.swapLock.lock() self.swapTime += 1 @@ -118,13 +123,18 @@ class MacCommon: Common { self.swapLock.unlock() } - if frameTimer != RENDER_TIMER_SYSTEM { - if let timer = self.timer, frameTimer == RENDER_TIMER_PRECISE { + if option.mac.macos_render_timer > RENDER_TIMER_SYSTEM { + if let timer = self.timer, option.mac.macos_render_timer == RENDER_TIMER_PRECISE { timer.scheduleAt(time: inOutputTime.pointee.hostTime, closure: signalSwap) return kCVReturnSuccess } signalSwap() + return kCVReturnSuccess + } + + if option.mac.macos_render_timer == RENDER_TIMER_PRESENTATION_FEEDBACK { + presentation?.add(time: inOutputTime.pointee) } return kCVReturnSuccess @@ -144,27 +154,16 @@ class MacCommon: Common { flagEvents(VO_EVENT_AMBIENT_LIGHTING_CHANGED) } - @objc override func updateICCProfile() { - guard let colorSpace = window?.screen?.colorSpace else { - log.sendWarning("Couldn't update ICC Profile, no color space available") - return - } - - layer?.colorspace = colorSpace.cgColorSpace + override func updateICCProfile() { flagEvents(VO_EVENT_ICC_PROFILE_CHANGED) } override func windowDidResize() { - guard let window = window else { - log.sendWarning("No window available on window resize event") - return - } - - updateRenderSize(window.framePixel.size) + flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE) } override func windowDidChangeScreenProfile() { - needsICCUpdate = true + updateICCProfile() } override func windowDidChangeBackingProperties() { diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c index ee26508..e3fc1ba 100644 --- a/video/out/opengl/common.c +++ b/video/out/opengl/common.c @@ -398,7 +398,7 @@ static const struct gl_functions gl_functions[] = { .provides = MPGL_CAP_NESTED_ARRAY, }, // Swap control, always an OS specific extension - // The OSX code loads this manually. + // The macOS code loads this manually. { .extension = "GLX_SGI_swap_control", .functions = (const struct gl_function[]) { diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c index 2db428f..ff4de18 100644 --- a/video/out/opengl/context_drm_egl.c +++ b/video/out/opengl/context_drm_egl.c @@ -616,6 +616,10 @@ static bool drm_egl_init(struct ra_ctx *ctx) xrgb_format = GBM_FORMAT_XBGR8888; break; default: + if (drm->opts->drm_format != DRM_OPTS_FORMAT_XRGB8888) { + MP_VERBOSE(ctx->vo, "Requested format not supported by context, " + "falling back to xrgb8888\n"); + } argb_format = GBM_FORMAT_ARGB8888; xrgb_format = GBM_FORMAT_XRGB8888; break; diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c index 4062224..a2a63e1 100644 --- a/video/out/opengl/context_glx.c +++ b/video/out/opengl/context_glx.c @@ -25,7 +25,7 @@ #define GLX_CONTEXT_FLAGS_ARB 0x2094 #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 #ifndef __APPLE__ -// These are respectively 0x00000001 and 0x00000002 on OSX +// These are respectively 0x00000001 and 0x00000002 on macOS #define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 #define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 #endif diff --git a/video/out/opengl/context_rpi.c b/video/out/opengl/context_rpi.c deleted file mode 100644 index 0b6babb..0000000 --- a/video/out/opengl/context_rpi.c +++ /dev/null @@ -1,327 +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 . - */ - -#include -#include -#include - -#include - -#include -#include - -#include "common/common.h" -#include "video/out/win_state.h" -#include "context.h" -#include "egl_helpers.h" - -struct priv { - struct GL gl; - DISPMANX_DISPLAY_HANDLE_T display; - DISPMANX_ELEMENT_HANDLE_T window; - DISPMANX_UPDATE_HANDLE_T update; - EGLDisplay egl_display; - EGLConfig egl_config; - EGLContext egl_context; - EGLSurface egl_surface; - // yep, the API keeps a pointer to it - EGL_DISPMANX_WINDOW_T egl_window; - int x, y, w, h; - double display_fps; - atomic_int reload_display; - int win_params[4]; -}; - -static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1, - uint32_t param2) -{ - 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 ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - - if (p->egl_surface) { - eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - eglDestroySurface(p->egl_display, p->egl_surface); - p->egl_surface = EGL_NO_SURFACE; - } - - if (p->window) - vc_dispmanx_element_remove(p->update, p->window); - p->window = 0; - if (p->display) - vc_dispmanx_display_close(p->display); - p->display = 0; - if (p->update) - vc_dispmanx_update_submit_sync(p->update); - p->update = 0; -} - -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); - - destroy_dispmanx(ctx); - - if (p->egl_context) - eglDestroyContext(p->egl_display, p->egl_context); - p->egl_context = EGL_NO_CONTEXT; - eglReleaseThread(); - p->egl_display = EGL_NO_DISPLAY; -} - -static bool recreate_dispmanx(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - int display_nr = 0; - int layer = 0; - - 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, "Could not get DISPMANX objects.\n"); - goto fail; - } - - uint32_t dispw, disph; - if (graphics_get_display_size(0, &dispw, &disph) < 0) { - MP_FATAL(ctx, "Could not get display size.\n"); - goto fail; - } - p->w = dispw; - p->h = disph; - - if (ctx->vo->opts->fullscreen) { - p->x = p->y = 0; - } else { - struct vo_win_geometry geo; - struct mp_rect screenrc = {0, 0, p->w, p->h}; - - vo_calc_window_geometry(ctx->vo, &screenrc, &geo); - - mp_rect_intersection(&geo.win, &screenrc); - - p->x = geo.win.x0; - p->y = geo.win.y0; - p->w = geo.win.x1 - geo.win.x0; - p->h = geo.win.y1 - geo.win.y0; - } - - // dispmanx is like a neanderthal version of Wayland - you can add an - // overlay any place on the screen. - VC_RECT_T dst = {.x = p->x, .y = p->y, .width = p->w, .height = p->h}; - VC_RECT_T src = {.width = p->w << 16, .height = p->h << 16}; - VC_DISPMANX_ALPHA_T alpha = { - .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, - .opacity = 0xFF, - }; - p->window = vc_dispmanx_element_add(p->update, p->display, layer, &dst, 0, - &src, DISPMANX_PROTECTION_NONE, &alpha, - 0, 0); - if (!p->window) { - MP_FATAL(ctx, "Could not add DISPMANX element.\n"); - goto fail; - } - - vc_dispmanx_update_submit_sync(p->update); - p->update = vc_dispmanx_update_start(0); - - p->egl_window = (EGL_DISPMANX_WINDOW_T){ - .element = p->window, - .width = p->w, - .height = p->h, - }; - p->egl_surface = eglCreateWindowSurface(p->egl_display, p->egl_config, - &p->egl_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; - } - - p->display_fps = 0; - TV_GET_STATE_RESP_T tvstate; - TV_DISPLAY_STATE_T tvstate_disp; - if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) { - if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) { - p->display_fps = tvstate_disp.display.hdmi.frame_rate; - - HDMI_PROPERTY_PARAM_T param = { - .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE, - }; - if (!vc_tv_hdmi_get_property(¶m) && - param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC) - p->display_fps = p->display_fps / 1.001; - } else { - p->display_fps = tvstate_disp.display.sdtv.frame_rate; - } - } - - p->win_params[0] = display_nr; - p->win_params[1] = layer; - p->win_params[2] = p->x; - p->win_params[3] = p->y; - - ctx->vo->dwidth = p->w; - ctx->vo->dheight = p->h; - if (ctx->swapchain) - 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 true; - -fail: - destroy_dispmanx(ctx); - return false; -} - -static void rpi_swap_buffers(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - 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(); - - vc_tv_register_callback(tv_callback, ctx); - - p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (!eglInitialize(p->egl_display, NULL, NULL)) { - MP_FATAL(ctx, "EGL failed to initialize.\n"); - goto fail; - } - - if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config)) - goto fail; - - if (!recreate_dispmanx(ctx)) - goto fail; - - mpegl_load_functions(&p->gl, ctx->log); - - struct ra_gl_ctx_params params = { - .swap_buffers = rpi_swap_buffers, - }; - - if (!ra_gl_ctx_init(ctx, &p->gl, params)) - goto fail; - - ra_add_native_resource(ctx->ra, "MPV_RPI_WINDOW", p->win_params); - - ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0); - return true; - -fail: - rpi_uninit(ctx); - return false; -} - -static bool rpi_reconfig(struct ra_ctx *ctx) -{ - return recreate_dispmanx(ctx); -} - -static struct mp_image *take_screenshot(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - - if (!p->display) - return NULL; - - struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h); - if (!img) - return NULL; - - DISPMANX_RESOURCE_HANDLE_T resource = - vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, - img->w | ((img->w * 4) << 16), img->h, - &(int32_t){0}); - if (!resource) - goto fail; - - if (vc_dispmanx_snapshot(p->display, resource, 0)) - goto fail; - - VC_RECT_T rc = {.width = img->w, .height = img->h}; - if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0])) - goto fail; - - vc_dispmanx_resource_delete(resource); - return img; - -fail: - vc_dispmanx_resource_delete(resource); - talloc_free(img); - return NULL; -} - -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 VO_TRUE; - case VOCTRL_CHECK_EVENTS: - if (atomic_fetch_and(&p->reload_display, 0)) { - MP_WARN(ctx, "Recovering from display mode switch...\n"); - recreate_dispmanx(ctx); - } - return VO_TRUE; - case VOCTRL_GET_DISPLAY_FPS: - *(double *)arg = p->display_fps; - return VO_TRUE; - } - - return VO_NOTIMPL; -} - -const struct ra_ctx_fns ra_ctx_rpi = { - .type = "opengl", - .name = "rpi", - .reconfig = rpi_reconfig, - .control = rpi_control, - .init = rpi_init, - .uninit = rpi_uninit, -}; diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c index 26c5268..2c5611b 100644 --- a/video/out/opengl/context_wayland.c +++ b/video/out/opengl/context_wayland.c @@ -47,14 +47,14 @@ static void resize(struct ra_ctx *ctx) const int32_t width = mp_rect_w(wl->geometry); const int32_t height = mp_rect_h(wl->geometry); + vo_wayland_handle_scale(wl); + vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha); if (p->egl_window) wl_egl_window_resize(p->egl_window, width, height, 0, 0); wl->vo->dwidth = width; wl->vo->dheight = height; - - vo_wayland_handle_fractional_scale(wl); } static bool wayland_egl_check_visible(struct ra_ctx *ctx) diff --git a/video/out/opengl/context_win.c b/video/out/opengl/context_win.c index 968b176..a582e28 100644 --- a/video/out/opengl/context_win.c +++ b/video/out/opengl/context_win.c @@ -96,6 +96,7 @@ static bool create_dc(struct ra_ctx *ctx) pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; + pfd.cAlphaBits = 8; pfd.iLayerType = PFD_MAIN_PLANE; int pf = ChoosePixelFormat(hdc, &pfd); @@ -293,6 +294,9 @@ static bool wgl_init(struct ra_ctx *ctx) if (!vo_w32_init(ctx->vo)) goto fail; + if (ctx->opts.want_alpha) + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); + vo_w32_run_on_thread(ctx->vo, create_ctx, ctx); if (!p->context) goto fail; @@ -368,11 +372,17 @@ static int wgl_control(struct ra_ctx *ctx, int *events, int request, void *arg) return ret; } +static void wgl_update_render_opts(struct ra_ctx *ctx) +{ + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); +} + const struct ra_ctx_fns ra_ctx_wgl = { - .type = "opengl", - .name = "win", - .init = wgl_init, - .reconfig = wgl_reconfig, - .control = wgl_control, - .uninit = wgl_uninit, + .type = "opengl", + .name = "win", + .init = wgl_init, + .reconfig = wgl_reconfig, + .control = wgl_control, + .update_render_opts = wgl_update_render_opts, + .uninit = wgl_uninit, }; diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c index 3bf6239..18d9027 100644 --- a/video/out/opengl/egl_helpers.c +++ b/video/out/opengl/egl_helpers.c @@ -192,18 +192,20 @@ static bool create_context(struct ra_ctx *ctx, EGLDisplay display, } if (!egl_ctx) { // Fallback for EGL 1.4 without EGL_KHR_create_context or GLES - // Add the context flags only for GLES - GL has been attempted above EGLint attrs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - es ? EGL_CONTEXT_FLAGS_KHR : EGL_NONE, ctx_flags, + EGL_CONTEXT_FLAGS_KHR, ctx_flags, + es ? EGL_CONTEXT_CLIENT_VERSION : EGL_NONE, 2, EGL_NONE }; egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); + if (!egl_ctx) + egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, &attrs[2]); } if (!egl_ctx) { - MP_MSG(ctx, msgl, "Could not create EGL context for %s!\n", name); + MP_MSG(ctx, msgl, "Could not create EGL context for %s (error=%d)!\n", + name, eglGetError()); return false; } diff --git a/video/out/opengl/formats.c b/video/out/opengl/formats.c index a0b79e2..dd0e89d 100644 --- a/video/out/opengl/formats.c +++ b/video/out/opengl/formats.c @@ -94,7 +94,7 @@ const struct gl_format gl_formats[] = { // Special formats. {"rgb565", GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, F_TF | F_GL2 | F_GL3}, - // Worthless, but needed by OSX videotoolbox interop on old Apple hardware. + // Worthless, but needed by macOS videotoolbox interop on old Apple hardware. {"appleyp", GL_RGB, GL_RGB_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, F_TF | F_APPL}, diff --git a/video/out/opengl/hwdec_rpi.c b/video/out/opengl/hwdec_rpi.c deleted file mode 100644 index 5362832..0000000 --- a/video/out/opengl/hwdec_rpi.c +++ /dev/null @@ -1,384 +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 . - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "common/common.h" -#include "common/msg.h" -#include "video/mp_image.h" -#include "video/out/gpu/hwdec.h" - -#include "common.h" - -struct priv { - struct mp_log *log; - - struct mp_image_params params; - - MMAL_COMPONENT_T *renderer; - bool renderer_enabled; - - // for RAM input - MMAL_POOL_T *swpool; - - struct mp_image *current_frame; - - struct mp_rect src, dst; - int cur_window[4]; // raw user params -}; - -// Magic alignments (in pixels) expected by the MMAL internals. -#define ALIGN_W 32 -#define ALIGN_H 16 - -// Make mpi point to buffer, assuming MMAL_ENCODING_I420. -// buffer can be NULL. -// Return the required buffer space. -static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, - struct mp_image_params *params) -{ - assert(params->imgfmt == IMGFMT_420P); - mp_image_set_params(mpi, params); - int w = MP_ALIGN_UP(params->w, ALIGN_W); - int h = MP_ALIGN_UP(params->h, ALIGN_H); - uint8_t *cur = buffer ? buffer->data : NULL; - size_t size = 0; - for (int i = 0; i < 3; i++) { - int div = i ? 2 : 1; - mpi->planes[i] = cur; - mpi->stride[i] = w / div; - size_t plane_size = h / div * mpi->stride[i]; - if (cur) - cur += plane_size; - size += plane_size; - } - return size; -} - -static MMAL_FOURCC_T map_csp(enum mp_csp csp) -{ - switch (csp) { - case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601; - case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709; - case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M; - default: return MMAL_COLOR_SPACE_UNKNOWN; - } -} - -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - mmal_buffer_header_release(buffer); -} - -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - struct mp_image *mpi = buffer->user_data; - talloc_free(mpi); -} - -static void disable_renderer(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - - if (p->renderer_enabled) { - mmal_port_disable(p->renderer->control); - mmal_port_disable(p->renderer->input[0]); - - mmal_port_flush(p->renderer->control); - mmal_port_flush(p->renderer->input[0]); - - mmal_component_disable(p->renderer); - } - mmal_pool_destroy(p->swpool); - p->swpool = NULL; - p->renderer_enabled = false; -} - -// check_window_only: assume params and dst/src rc are unchanged -static void update_overlay(struct ra_hwdec *hw, bool check_window_only) -{ - struct priv *p = hw->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - struct mp_rect src = p->src; - struct mp_rect dst = p->dst; - - int defs[4] = {0, 0, 0, 0}; - int *z = ra_get_native_resource(hw->ra_ctx->ra, "MPV_RPI_WINDOW"); - if (!z) - z = defs; - - // As documented in the libmpv openglcb headers. - int display = z[0]; - int layer = z[1]; - int x = z[2]; - int y = z[3]; - - if (check_window_only && memcmp(z, p->cur_window, sizeof(p->cur_window)) == 0) - return; - - memcpy(p->cur_window, z, sizeof(p->cur_window)); - - int rotate[] = {MMAL_DISPLAY_ROT0, - MMAL_DISPLAY_ROT90, - MMAL_DISPLAY_ROT180, - MMAL_DISPLAY_ROT270}; - - int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0, - dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0; - int p_x, p_y; - av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000); - MMAL_DISPLAYREGION_T dr = { - .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION, - .size = sizeof(MMAL_DISPLAYREGION_T), }, - .src_rect = { .x = src.x0, .y = src.y0, - .width = src_w, .height = src_h }, - .dest_rect = { .x = dst.x0 + x, .y = dst.y0 + y, - .width = dst_w, .height = dst_h }, - .layer = layer - 1, // under the GL layer - .display_num = display, - .pixel_x = p_x, - .pixel_y = p_y, - .transform = rotate[p->params.rotate / 90], - .fullscreen = 0, - .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT | - MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM | - MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM | - MMAL_DISPLAY_SET_FULLSCREEN, - }; - - if (p->params.rotate % 180 == 90) { - MPSWAP(int, dr.src_rect.x, dr.src_rect.y); - MPSWAP(int, dr.src_rect.width, dr.src_rect.height); - } - - if (mmal_port_parameter_set(input, &dr.hdr)) - MP_WARN(p, "could not set video rectangle\n"); -} - -static int enable_renderer(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - struct mp_image_params *params = &p->params; - - if (p->renderer_enabled) - return 0; - - if (!params->imgfmt) - return -1; - - bool opaque = params->imgfmt == IMGFMT_MMAL; - - input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420; - input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W); - input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H); - input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h}; - input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h}; - input->format->es->video.color_space = map_csp(params->color.space); - - if (mmal_port_format_commit(input)) - return -1; - - input->buffer_num = MPMAX(input->buffer_num_min, - input->buffer_num_recommended) + 3; - input->buffer_size = MPMAX(input->buffer_size_min, - input->buffer_size_recommended); - - if (!opaque) { - size_t size = layout_buffer(&(struct mp_image){0}, NULL, params); - if (input->buffer_size != size) { - MP_FATAL(hw, "We disagree with MMAL about buffer sizes.\n"); - return -1; - } - - p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size); - if (!p->swpool) { - MP_FATAL(hw, "Could not allocate buffer pool.\n"); - return -1; - } - } - - update_overlay(hw, false); - - p->renderer_enabled = true; - - if (mmal_port_enable(p->renderer->control, control_port_cb)) - return -1; - - if (mmal_port_enable(input, input_port_cb)) - return -1; - - if (mmal_component_enable(p->renderer)) { - MP_FATAL(hw, "Failed to enable video renderer.\n"); - return -1; - } - - return 0; -} - -static void free_mmal_buffer(void *arg) -{ - MMAL_BUFFER_HEADER_T *buffer = arg; - mmal_buffer_header_release(buffer); -} - -static struct mp_image *upload(struct ra_hwdec *hw, struct mp_image *hw_image) -{ - struct priv *p = hw->priv; - - MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue); - if (!buffer) { - MP_ERR(hw, "Can't allocate buffer.\n"); - return NULL; - } - mmal_buffer_header_reset(buffer); - - struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer, - free_mmal_buffer); - if (!new_ref) { - mmal_buffer_header_release(buffer); - MP_ERR(hw, "Out of memory.\n"); - return NULL; - } - - mp_image_setfmt(new_ref, IMGFMT_MMAL); - new_ref->planes[3] = (void *)buffer; - - struct mp_image dmpi = {0}; - buffer->length = layout_buffer(&dmpi, buffer, &p->params); - mp_image_copy(&dmpi, hw_image); - - return new_ref; -} - -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; - - if (hw_image && !mp_image_params_equal(&p->params, &hw_image->params)) { - p->params = hw_image->params; - - disable_renderer(hw); - mp_image_unrefp(&p->current_frame); - - if (enable_renderer(hw) < 0) - return -1; - } - - if (hw_image && p->current_frame && !newframe) { - if (!mp_rect_equals(&p->src, src) ||mp_rect_equals(&p->dst, dst)) { - p->src = *src; - p->dst = *dst; - update_overlay(hw, false); - } - return 0; // don't reupload - } - - mp_image_unrefp(&p->current_frame); - - if (!hw_image) { - disable_renderer(hw); - return 0; - } - - if (enable_renderer(hw) < 0) - return -1; - - update_overlay(hw, true); - - struct mp_image *mpi = NULL; - if (hw_image->imgfmt == IMGFMT_MMAL) { - mpi = mp_image_new_ref(hw_image); - } else { - mpi = upload(hw, hw_image); - } - - if (!mpi) { - disable_renderer(hw); - return -1; - } - - MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3]; - - // Assume this field is free for use by us. - ref->user_data = mpi; - - if (mmal_port_send_buffer(p->renderer->input[0], ref)) { - MP_ERR(hw, "could not queue picture!\n"); - talloc_free(mpi); - return -1; - } - - return 0; -} - -static void destroy(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - - disable_renderer(hw); - - if (p->renderer) - mmal_component_release(p->renderer); - - mmal_vc_deinit(); -} - -static int create(struct ra_hwdec *hw) -{ - struct priv *p = hw->priv; - p->log = hw->log; - - bcm_host_init(); - - if (mmal_vc_init()) { - MP_FATAL(hw, "Could not initialize MMAL.\n"); - return -1; - } - - if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer)) - { - MP_FATAL(hw, "Could not create MMAL renderer.\n"); - mmal_vc_deinit(); - return -1; - } - - return 0; -} - -const struct ra_hwdec_driver ra_hwdec_rpi_overlay = { - .name = "rpi-overlay", - .priv_size = sizeof(struct priv), - .imgfmts = {IMGFMT_MMAL, IMGFMT_420P, 0}, - .init = create, - .overlay_frame = overlay_frame, - .uninit = destroy, -}; diff --git a/video/out/placebo/ra_pl.c b/video/out/placebo/ra_pl.c index 6259651..14c9444 100644 --- a/video/out/placebo/ra_pl.c +++ b/video/out/placebo/ra_pl.c @@ -534,7 +534,7 @@ static void renderpass_run_pl(struct ra *ra, .data = val->data, }); } else { - struct pl_desc_binding bind; + struct pl_desc_binding bind = {0}; switch (inp->type) { case RA_VARTYPE_TEX: case RA_VARTYPE_IMG_W: { diff --git a/video/out/placebo/utils.c b/video/out/placebo/utils.c index 1209b72..dae9b14 100644 --- a/video/out/placebo/utils.c +++ b/video/out/placebo/utils.c @@ -25,7 +25,7 @@ static const enum pl_log_level msg_lev_to_pl_log[MSGL_MAX+1] = { }; // translates log levels while probing -static const enum pl_log_level probing_map(enum pl_log_level level) +static enum pl_log_level probing_map(enum pl_log_level level) { switch (level) { case PL_LOG_FATAL: @@ -44,6 +44,12 @@ static void log_cb(void *priv, enum pl_log_level level, const char *msg) mp_msg(log, pl_log_to_msg_lev[level], "%s\n", msg); } +static int determine_pl_log_level(struct mp_log *log) +{ + int log_level = mp_msg_level(log); + return log_level == -1 ? PL_LOG_NONE : msg_lev_to_pl_log[log_level]; +} + static void log_cb_probing(void *priv, enum pl_log_level level, const char *msg) { struct mp_log *log = priv; @@ -54,7 +60,7 @@ pl_log mppl_log_create(void *tactx, struct mp_log *log) { return pl_log_create(PL_API_VER, &(struct pl_log_params) { .log_cb = log_cb, - .log_level = msg_lev_to_pl_log[mp_msg_level(log)], + .log_level = determine_pl_log_level(log), .log_priv = mp_log_new(tactx, log, "libplacebo"), }); } @@ -65,199 +71,3 @@ void mppl_log_set_probing(pl_log log, bool probing) params.log_cb = probing ? log_cb_probing : log_cb; pl_log_update(log, ¶ms); } - -enum pl_color_primaries mp_prim_to_pl(enum mp_csp_prim prim) -{ - switch (prim) { - case MP_CSP_PRIM_AUTO: return PL_COLOR_PRIM_UNKNOWN; - case MP_CSP_PRIM_BT_601_525: return PL_COLOR_PRIM_BT_601_525; - case MP_CSP_PRIM_BT_601_625: return PL_COLOR_PRIM_BT_601_625; - case MP_CSP_PRIM_BT_709: return PL_COLOR_PRIM_BT_709; - case MP_CSP_PRIM_BT_2020: return PL_COLOR_PRIM_BT_2020; - case MP_CSP_PRIM_BT_470M: return PL_COLOR_PRIM_BT_470M; - case MP_CSP_PRIM_APPLE: return PL_COLOR_PRIM_APPLE; - case MP_CSP_PRIM_ADOBE: return PL_COLOR_PRIM_ADOBE; - case MP_CSP_PRIM_PRO_PHOTO: return PL_COLOR_PRIM_PRO_PHOTO; - case MP_CSP_PRIM_CIE_1931: return PL_COLOR_PRIM_CIE_1931; - case MP_CSP_PRIM_DCI_P3: return PL_COLOR_PRIM_DCI_P3; - case MP_CSP_PRIM_DISPLAY_P3: return PL_COLOR_PRIM_DISPLAY_P3; - case MP_CSP_PRIM_V_GAMUT: return PL_COLOR_PRIM_V_GAMUT; - case MP_CSP_PRIM_S_GAMUT: return PL_COLOR_PRIM_S_GAMUT; - case MP_CSP_PRIM_EBU_3213: return PL_COLOR_PRIM_EBU_3213; - case MP_CSP_PRIM_FILM_C: return PL_COLOR_PRIM_FILM_C; - case MP_CSP_PRIM_ACES_AP0: return PL_COLOR_PRIM_ACES_AP0; - case MP_CSP_PRIM_ACES_AP1: return PL_COLOR_PRIM_ACES_AP1; - case MP_CSP_PRIM_COUNT: return PL_COLOR_PRIM_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum mp_csp_prim mp_prim_from_pl(enum pl_color_primaries prim) -{ - switch (prim){ - case PL_COLOR_PRIM_UNKNOWN: return MP_CSP_PRIM_AUTO; - case PL_COLOR_PRIM_BT_601_525: return MP_CSP_PRIM_BT_601_525; - case PL_COLOR_PRIM_BT_601_625: return MP_CSP_PRIM_BT_601_625; - case PL_COLOR_PRIM_BT_709: return MP_CSP_PRIM_BT_709; - case PL_COLOR_PRIM_BT_2020: return MP_CSP_PRIM_BT_2020; - case PL_COLOR_PRIM_BT_470M: return MP_CSP_PRIM_BT_470M; - case PL_COLOR_PRIM_APPLE: return MP_CSP_PRIM_APPLE; - case PL_COLOR_PRIM_ADOBE: return MP_CSP_PRIM_ADOBE; - case PL_COLOR_PRIM_PRO_PHOTO: return MP_CSP_PRIM_PRO_PHOTO; - case PL_COLOR_PRIM_CIE_1931: return MP_CSP_PRIM_CIE_1931; - case PL_COLOR_PRIM_DCI_P3: return MP_CSP_PRIM_DCI_P3; - case PL_COLOR_PRIM_DISPLAY_P3: return MP_CSP_PRIM_DISPLAY_P3; - case PL_COLOR_PRIM_V_GAMUT: return MP_CSP_PRIM_V_GAMUT; - case PL_COLOR_PRIM_S_GAMUT: return MP_CSP_PRIM_S_GAMUT; - case PL_COLOR_PRIM_EBU_3213: return MP_CSP_PRIM_EBU_3213; - case PL_COLOR_PRIM_FILM_C: return MP_CSP_PRIM_FILM_C; - case PL_COLOR_PRIM_ACES_AP0: return MP_CSP_PRIM_ACES_AP0; - case PL_COLOR_PRIM_ACES_AP1: return MP_CSP_PRIM_ACES_AP1; - case PL_COLOR_PRIM_COUNT: return MP_CSP_PRIM_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_color_transfer mp_trc_to_pl(enum mp_csp_trc trc) -{ - switch (trc) { - case MP_CSP_TRC_AUTO: return PL_COLOR_TRC_UNKNOWN; - case MP_CSP_TRC_BT_1886: return PL_COLOR_TRC_BT_1886; - case MP_CSP_TRC_SRGB: return PL_COLOR_TRC_SRGB; - case MP_CSP_TRC_LINEAR: return PL_COLOR_TRC_LINEAR; - case MP_CSP_TRC_GAMMA18: return PL_COLOR_TRC_GAMMA18; - case MP_CSP_TRC_GAMMA20: return PL_COLOR_TRC_GAMMA20; - case MP_CSP_TRC_GAMMA22: return PL_COLOR_TRC_GAMMA22; - case MP_CSP_TRC_GAMMA24: return PL_COLOR_TRC_GAMMA24; - case MP_CSP_TRC_GAMMA26: return PL_COLOR_TRC_GAMMA26; - case MP_CSP_TRC_GAMMA28: return PL_COLOR_TRC_GAMMA28; - case MP_CSP_TRC_PRO_PHOTO: return PL_COLOR_TRC_PRO_PHOTO; - case MP_CSP_TRC_PQ: return PL_COLOR_TRC_PQ; - case MP_CSP_TRC_HLG: return PL_COLOR_TRC_HLG; - case MP_CSP_TRC_V_LOG: return PL_COLOR_TRC_V_LOG; - case MP_CSP_TRC_S_LOG1: return PL_COLOR_TRC_S_LOG1; - case MP_CSP_TRC_S_LOG2: return PL_COLOR_TRC_S_LOG2; - case MP_CSP_TRC_ST428: return PL_COLOR_TRC_ST428; - case MP_CSP_TRC_COUNT: return PL_COLOR_TRC_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum mp_csp_trc mp_trc_from_pl(enum pl_color_transfer trc) -{ - switch (trc){ - case PL_COLOR_TRC_UNKNOWN: return MP_CSP_TRC_AUTO; - case PL_COLOR_TRC_BT_1886: return MP_CSP_TRC_BT_1886; - case PL_COLOR_TRC_SRGB: return MP_CSP_TRC_SRGB; - case PL_COLOR_TRC_LINEAR: return MP_CSP_TRC_LINEAR; - case PL_COLOR_TRC_GAMMA18: return MP_CSP_TRC_GAMMA18; - case PL_COLOR_TRC_GAMMA20: return MP_CSP_TRC_GAMMA20; - case PL_COLOR_TRC_GAMMA22: return MP_CSP_TRC_GAMMA22; - case PL_COLOR_TRC_GAMMA24: return MP_CSP_TRC_GAMMA24; - case PL_COLOR_TRC_GAMMA26: return MP_CSP_TRC_GAMMA26; - case PL_COLOR_TRC_GAMMA28: return MP_CSP_TRC_GAMMA28; - case PL_COLOR_TRC_PRO_PHOTO: return MP_CSP_TRC_PRO_PHOTO; - case PL_COLOR_TRC_PQ: return MP_CSP_TRC_PQ; - case PL_COLOR_TRC_HLG: return MP_CSP_TRC_HLG; - case PL_COLOR_TRC_V_LOG: return MP_CSP_TRC_V_LOG; - case PL_COLOR_TRC_S_LOG1: return MP_CSP_TRC_S_LOG1; - case PL_COLOR_TRC_S_LOG2: return MP_CSP_TRC_S_LOG2; - case PL_COLOR_TRC_ST428: return MP_CSP_TRC_ST428; - case PL_COLOR_TRC_COUNT: return MP_CSP_TRC_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_color_system mp_csp_to_pl(enum mp_csp csp) -{ - switch (csp) { - case MP_CSP_AUTO: return PL_COLOR_SYSTEM_UNKNOWN; - case MP_CSP_BT_601: return PL_COLOR_SYSTEM_BT_601; - case MP_CSP_BT_709: return PL_COLOR_SYSTEM_BT_709; - case MP_CSP_SMPTE_240M: return PL_COLOR_SYSTEM_SMPTE_240M; - case MP_CSP_BT_2020_NC: return PL_COLOR_SYSTEM_BT_2020_NC; - case MP_CSP_BT_2020_C: return PL_COLOR_SYSTEM_BT_2020_C; - case MP_CSP_RGB: return PL_COLOR_SYSTEM_RGB; - case MP_CSP_XYZ: return PL_COLOR_SYSTEM_XYZ; - case MP_CSP_YCGCO: return PL_COLOR_SYSTEM_YCGCO; - case MP_CSP_COUNT: return PL_COLOR_SYSTEM_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_color_levels mp_levels_to_pl(enum mp_csp_levels levels) -{ - switch (levels) { - case MP_CSP_LEVELS_AUTO: return PL_COLOR_LEVELS_UNKNOWN; - case MP_CSP_LEVELS_TV: return PL_COLOR_LEVELS_TV; - case MP_CSP_LEVELS_PC: return PL_COLOR_LEVELS_PC; - case MP_CSP_LEVELS_COUNT: return PL_COLOR_LEVELS_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum mp_csp_levels mp_levels_from_pl(enum pl_color_levels levels) -{ - switch (levels){ - case PL_COLOR_LEVELS_UNKNOWN: return MP_CSP_LEVELS_AUTO; - case PL_COLOR_LEVELS_TV: return MP_CSP_LEVELS_TV; - case PL_COLOR_LEVELS_PC: return MP_CSP_LEVELS_PC; - case PL_COLOR_LEVELS_COUNT: return MP_CSP_LEVELS_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_alpha_mode mp_alpha_to_pl(enum mp_alpha_type alpha) -{ - switch (alpha) { - case MP_ALPHA_AUTO: return PL_ALPHA_UNKNOWN; - case MP_ALPHA_STRAIGHT: return PL_ALPHA_INDEPENDENT; - case MP_ALPHA_PREMUL: return PL_ALPHA_PREMULTIPLIED; - } - - MP_ASSERT_UNREACHABLE(); -} - -enum pl_chroma_location mp_chroma_to_pl(enum mp_chroma_location chroma) -{ - switch (chroma) { - case MP_CHROMA_AUTO: return PL_CHROMA_UNKNOWN; - case MP_CHROMA_TOPLEFT: return PL_CHROMA_TOP_LEFT; - case MP_CHROMA_LEFT: return PL_CHROMA_LEFT; - case MP_CHROMA_CENTER: return PL_CHROMA_CENTER; - case MP_CHROMA_COUNT: return PL_CHROMA_COUNT; - } - - MP_ASSERT_UNREACHABLE(); -} - -void mp_map_dovi_metadata_to_pl(struct mp_image *mpi, - struct pl_frame *frame) -{ -#ifdef PL_HAVE_LAV_DOLBY_VISION - if (mpi->dovi) { - const AVDOVIMetadata *metadata = (AVDOVIMetadata *) mpi->dovi->data; - const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata); - - if (header->disable_residual_flag) { - // Only automatically map DoVi RPUs that don't require an EL - struct pl_dovi_metadata *dovi = talloc_ptrtype(mpi, dovi); - pl_frame_map_avdovi_metadata(frame, dovi, metadata); - } - } - -#if defined(PL_HAVE_LIBDOVI) - if (mpi->dovi_buf) - pl_hdr_metadata_from_dovi_rpu(&frame->color.hdr, mpi->dovi_buf->data, - mpi->dovi_buf->size); -#endif - -#endif // PL_HAVE_LAV_DOLBY_VISION -} diff --git a/video/out/placebo/utils.h b/video/out/placebo/utils.h index bf780a8..3f61d8b 100644 --- a/video/out/placebo/utils.h +++ b/video/out/placebo/utils.h @@ -26,16 +26,3 @@ static inline struct pl_rect2d mp_rect2d_to_pl(struct mp_rect rc) .y1 = rc.y1, }; } - -enum pl_color_primaries mp_prim_to_pl(enum mp_csp_prim prim); -enum mp_csp_prim mp_prim_from_pl(enum pl_color_primaries prim); -enum pl_color_transfer mp_trc_to_pl(enum mp_csp_trc trc); -enum mp_csp_trc mp_trc_from_pl(enum pl_color_transfer trc); -enum pl_color_system mp_csp_to_pl(enum mp_csp csp); -enum pl_color_levels mp_levels_to_pl(enum mp_csp_levels levels); -enum mp_csp_levels mp_levels_from_pl(enum pl_color_levels levels); -enum pl_alpha_mode mp_alpha_to_pl(enum mp_alpha_type alpha); -enum pl_chroma_location mp_chroma_to_pl(enum mp_chroma_location chroma); - -void mp_map_dovi_metadata_to_pl(struct mp_image *mpi, - struct pl_frame *frame); diff --git a/video/out/vo.c b/video/out/vo.c index 50129fb..db29690 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -63,14 +63,12 @@ extern const struct vo_driver video_out_sdl; extern const struct vo_driver video_out_vaapi; extern const struct vo_driver video_out_dmabuf_wayland; extern const struct vo_driver video_out_wlshm; -extern const struct vo_driver video_out_rpi; extern const struct vo_driver video_out_tct; extern const struct vo_driver video_out_sixel; extern const struct vo_driver video_out_kitty; static const struct vo_driver *const video_out_drivers[] = { - &video_out_libmpv, #if HAVE_ANDROID &video_out_mediacodec_embed, #endif @@ -100,6 +98,7 @@ static const struct vo_driver *const video_out_drivers[] = #if HAVE_X11 &video_out_x11, #endif + &video_out_libmpv, &video_out_null, // should not be auto-selected &video_out_image, @@ -110,9 +109,6 @@ static const struct vo_driver *const video_out_drivers[] = #if HAVE_DRM &video_out_drm, #endif -#if HAVE_RPI_MMAL - &video_out_rpi, -#endif #if HAVE_SIXEL &video_out_sixel, #endif @@ -138,6 +134,7 @@ struct vo_internal { bool want_redraw; // redraw request from VO to player bool send_reset; // send VOCTRL_RESET bool paused; + bool wakeup_on_done; int queued_events; // event mask for the user int internal_events; // event mask for us @@ -239,7 +236,6 @@ static void update_opts(void *p) if (m_config_cache_update(vo->opts_cache)) { read_opts(vo); - if (vo->driver->control) { vo->driver->control(vo, VOCTRL_VO_OPTS_CHANGED, NULL); // "Legacy" update of video position related options. @@ -247,18 +243,6 @@ static void update_opts(void *p) vo->driver->control(vo, VOCTRL_SET_PANSCAN, NULL); } } - - if (vo->gl_opts_cache && m_config_cache_update(vo->gl_opts_cache)) { - // "Legacy" update of video GL renderer related options. - if (vo->driver->control) - vo->driver->control(vo, VOCTRL_UPDATE_RENDER_OPTS, NULL); - } - - if (m_config_cache_update(vo->eq_opts_cache)) { - // "Legacy" update of video equalizer related options. - if (vo->driver->control) - vo->driver->control(vo, VOCTRL_SET_EQUALIZER, NULL); - } } // Does not include thread- and VO uninit. @@ -270,6 +254,7 @@ static void dealloc_vo(struct vo *vo) talloc_free(vo->opts_cache); talloc_free(vo->gl_opts_cache); talloc_free(vo->eq_opts_cache); + mp_mutex_destroy(&vo->params_mutex); mp_mutex_destroy(&vo->in->lock); mp_cond_destroy(&vo->in->wakeup); @@ -301,6 +286,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, .probing = probing, .in = talloc(vo, struct vo_internal), }; + mp_mutex_init(&vo->params_mutex); talloc_steal(vo, log); *vo->in = (struct vo_internal) { .dispatch = mp_dispatch_create(vo), @@ -319,12 +305,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, update_opts, vo); vo->gl_opts_cache = m_config_cache_alloc(NULL, global, &gl_video_conf); - m_config_cache_set_dispatch_change_cb(vo->gl_opts_cache, vo->in->dispatch, - update_opts, vo); - vo->eq_opts_cache = m_config_cache_alloc(NULL, global, &mp_csp_equalizer_conf); - m_config_cache_set_dispatch_change_cb(vo->eq_opts_cache, vo->in->dispatch, - update_opts, vo); mp_input_set_mouse_transform(vo->input_ctx, NULL, NULL); if (vo->driver->encode != !!vo->encode_lavc_ctx) @@ -613,8 +594,10 @@ static void run_reconfig(void *p) mp_image_params_get_dsize(params, &vo->dwidth, &vo->dheight); + mp_mutex_lock(&vo->params_mutex); talloc_free(vo->params); vo->params = talloc_dup(vo, params); + mp_mutex_unlock(&vo->params_mutex); if (vo->driver->reconfig2) { *ret = vo->driver->reconfig2(vo, img); @@ -625,8 +608,10 @@ static void run_reconfig(void *p) if (vo->config_ok) { check_vo_caps(vo); } else { + mp_mutex_lock(&vo->params_mutex); talloc_free(vo->params); vo->params = NULL; + mp_mutex_unlock(&vo->params_mutex); } mp_mutex_lock(&in->lock); @@ -765,6 +750,52 @@ void vo_wakeup(struct vo *vo) mp_mutex_unlock(&in->lock); } +static int64_t get_current_frame_end(struct vo *vo) +{ + struct vo_internal *in = vo->in; + if (!in->current_frame) + return -1; + return in->current_frame->pts + MPMAX(in->current_frame->duration, 0); +} + +static bool still_displaying(struct vo *vo) +{ + struct vo_internal *in = vo->in; + bool working = in->rendering || in->frame_queued; + if (working) + goto done; + + int64_t frame_end = get_current_frame_end(vo); + if (frame_end < 0) + goto done; + working = mp_time_ns() < frame_end; + +done: + return working && in->hasframe; +} + +// Return true if there is still a frame being displayed (or queued). +bool vo_still_displaying(struct vo *vo) +{ + mp_mutex_lock(&vo->in->lock); + bool res = still_displaying(vo); + mp_mutex_unlock(&vo->in->lock); + return res; +} + +// Make vo issue a wakeup once vo_still_displaying() becomes false. +void vo_request_wakeup_on_done(struct vo *vo) +{ + struct vo_internal *in = vo->in; + mp_mutex_lock(&vo->in->lock); + if (still_displaying(vo)) { + in->wakeup_on_done = true; + } else { + wakeup_core(vo); + } + mp_mutex_unlock(&vo->in->lock); +} + // Whether vo_queue_frame() can be called. If the VO is not ready yet, the // function will return false, and the VO will call the wakeup callback once // it's ready. @@ -923,6 +954,7 @@ static bool render_frame(struct vo *vo) if (in->dropped_frame) { in->drop_count += 1; + wakeup_core(vo); } else { in->rendering = true; in->hasframe_rendered = true; @@ -994,10 +1026,9 @@ static bool render_frame(struct vo *vo) more_frames = true; mp_cond_broadcast(&in->wakeup); // for vo_wait_frame() - wakeup_core(vo); done: - if (!vo->driver->frame_owner) + if (!vo->driver->frame_owner || in->dropped_frame) talloc_free(frame); mp_mutex_unlock(&in->lock); @@ -1074,6 +1105,8 @@ static MP_THREAD_VOID vo_thread(void *ptr) bool working = render_frame(vo); int64_t now = mp_time_ns(); int64_t wait_until = now + MP_TIME_S_TO_NS(working ? 0 : 1000); + bool wakeup_on_done = false; + int64_t wakeup_core_after = 0; mp_mutex_lock(&in->lock); if (in->wakeup_pts) { @@ -1088,6 +1121,14 @@ static MP_THREAD_VOID vo_thread(void *ptr) in->want_redraw = true; wakeup_core(vo); } + if ((!working && !in->rendering && !in->frame_queued) && in->wakeup_on_done) { + // At this point we know VO is going to sleep + int64_t frame_end = get_current_frame_end(vo); + if (frame_end >= 0) + wakeup_core_after = frame_end; + wakeup_on_done = true; + in->wakeup_on_done = false; + } vo->want_redraw = false; bool redraw = in->request_redraw; bool send_reset = in->send_reset; @@ -1110,6 +1151,17 @@ static MP_THREAD_VOID vo_thread(void *ptr) if (wait_until <= now) continue; + if (wakeup_on_done) { + // At this point wait_until should be longer than frame duration + if (wakeup_core_after >= 0 && wait_until >= wakeup_core_after) { + wait_vo(vo, wakeup_core_after); + mp_mutex_lock(&in->lock); + in->need_wakeup = true; + mp_mutex_unlock(&in->lock); + } + wakeup_core(vo); + } + wait_vo(vo, wait_until); } forget_frames(vo); // implicitly synchronized @@ -1185,17 +1237,6 @@ void vo_seek_reset(struct vo *vo) mp_mutex_unlock(&in->lock); } -// Return true if there is still a frame being displayed (or queued). -// If this returns true, a wakeup some time in the future is guaranteed. -bool vo_still_displaying(struct vo *vo) -{ - struct vo_internal *in = vo->in; - mp_mutex_lock(&in->lock); - bool working = in->rendering || in->frame_queued; - mp_mutex_unlock(&in->lock); - return working && in->hasframe; -} - // Whether at least 1 frame was queued or rendered since last seek or reconfig. bool vo_has_frame(struct vo *vo) { @@ -1433,9 +1474,19 @@ int lookup_keymap_table(const struct mp_keymap *map, int key) struct mp_image_params vo_get_current_params(struct vo *vo) { struct mp_image_params p = {0}; - mp_mutex_lock(&vo->in->lock); + mp_mutex_lock(&vo->params_mutex); if (vo->params) p = *vo->params; - mp_mutex_unlock(&vo->in->lock); + mp_mutex_unlock(&vo->params_mutex); + return p; +} + +struct mp_image_params vo_get_target_params(struct vo *vo) +{ + struct mp_image_params p = {0}; + mp_mutex_lock(&vo->params_mutex); + if (vo->target_params) + p = *vo->target_params; + mp_mutex_unlock(&vo->params_mutex); return p; } diff --git a/video/out/vo.h b/video/out/vo.h index e38dcf8..313c08a 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -29,6 +29,7 @@ #include "video/img_format.h" #include "common/common.h" #include "options/options.h" +#include "osdep/threads.h" enum { // VO needs to redraw @@ -66,7 +67,6 @@ enum mp_voctrl { VOCTRL_RESUME, VOCTRL_SET_PANSCAN, - VOCTRL_SET_EQUALIZER, // Triggered by any change to mp_vo_opts. This is for convenience. In theory, // you could install your own listener. @@ -122,6 +122,13 @@ enum mp_voctrl { /* private to vo_gpu and vo_gpu_next */ VOCTRL_EXTERNAL_RESIZE, + + // Begin VO dragging. + VOCTRL_BEGIN_DRAGGING, + + // Native context menu + VOCTRL_SHOW_MENU, + VOCTRL_UPDATE_MENU, }; // Helper to expose what kind of content is currently playing to the VO. @@ -210,7 +217,7 @@ struct vo_frame { // If 0, present immediately. int64_t pts; // Approximate frame duration, in ns. - int duration; + double duration; // Realtime of estimated distance between 2 vsync events. double vsync_interval; // "ideal" display time within the vsync @@ -470,7 +477,17 @@ struct vo { // be accessed unsynchronized (read-only). int config_ok; // Last config call was successful? - struct mp_image_params *params; // Configured parameters (as in vo_reconfig) + + // --- The following fields are synchronized by params_mutex, most of + // the params are set only in the vo_reconfig and safe to read + // unsynchronized. Some of the parameters are updated in draw_frame, + // which are still safe to read in the play loop, but for correctness + // generic getter is protected by params_mutex. + mp_mutex params_mutex; + // Configured parameters (changed in vo_reconfig) + struct mp_image_params *params; + // Target display parameters (VO is responsible for re-/setting) + struct mp_image_params *target_params; // --- The following fields can be accessed only by the VO thread, or from // anywhere _if_ the VO thread is suspended (use vo->dispatch). @@ -486,6 +503,9 @@ struct vo { int dwidth; int dheight; float monitor_par; + + // current GPU context (--vo=gpu and --vo=gpu-next only) + const char *context_name; }; struct mpv_global; @@ -499,6 +519,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts); void vo_queue_frame(struct vo *vo, struct vo_frame *frame); void vo_wait_frame(struct vo *vo); bool vo_still_displaying(struct vo *vo); +void vo_request_wakeup_on_done(struct vo *vo); bool vo_has_frame(struct vo *vo); void vo_redraw(struct vo *vo); bool vo_want_redraw(struct vo *vo); @@ -540,5 +561,6 @@ void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, struct vo_frame *vo_frame_ref(struct vo_frame *frame); struct mp_image_params vo_get_current_params(struct vo *vo); +struct mp_image_params vo_get_target_params(struct vo *vo); #endif /* MPLAYER_VIDEO_OUT_H */ diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c index 16936bb..91e962f 100644 --- a/video/out/vo_direct3d.c +++ b/video/out/vo_direct3d.c @@ -102,6 +102,7 @@ typedef struct d3d_priv { struct mp_osd_res osd_res; int image_format; /**< mplayer image format */ struct mp_image_params params; + struct mp_image_params dst_params; D3DFORMAT movie_src_fmt; /**< Movie colorspace format (depends on the movie's codec) */ @@ -896,6 +897,18 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (!resize_d3d(priv)) return VO_ERROR; + priv->dst_params = *params; + for (const struct fmt_entry *cur = &fmt_table[0]; cur->mplayer_fmt; ++cur) { + if (cur->fourcc == priv->desktop_fmt) { + priv->dst_params.imgfmt = cur->mplayer_fmt; + break; + } + } + mp_image_params_guess_csp(&priv->dst_params); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &priv->dst_params; + mp_mutex_unlock(&vo->params_mutex); + return 0; /* Success */ } diff --git a/video/out/vo_dmabuf_wayland.c b/video/out/vo_dmabuf_wayland.c index e04ff5d..35a4dac 100644 --- a/video/out/vo_dmabuf_wayland.c +++ b/video/out/vo_dmabuf_wayland.c @@ -137,10 +137,10 @@ static const struct wl_buffer_listener osd_buffer_listener = { }; #if HAVE_VAAPI -static void close_file_descriptors(VADRMPRIMESurfaceDescriptor desc) +static void close_file_descriptors(const VADRMPRIMESurfaceDescriptor *desc) { - for (int i = 0; i < desc.num_objects; i++) - close(desc.objects[i].fd); + for (int i = 0; i < desc->num_objects; i++) + close(desc->objects[i].fd); } #endif @@ -175,7 +175,7 @@ static bool vaapi_drm_format(struct vo *vo, struct mp_image *src) p->drm_modifier = desc.objects[0].drm_format_modifier; format = true; done: - close_file_descriptors(desc); + close_file_descriptors(&desc); #endif return format; } @@ -216,7 +216,7 @@ static void vaapi_dmabuf_importer(struct buffer *buf, struct mp_image *src, } done: - close_file_descriptors(desc); + close_file_descriptors(&desc); #endif } @@ -493,7 +493,7 @@ static void set_viewport_source(struct vo *vo, struct mp_rect src) if (p->force_window) return; - if (wl->video_viewport && !mp_rect_equals(&p->src, &src)) { + if (!mp_rect_equals(&p->src, &src)) { wp_viewport_set_source(wl->video_viewport, src.x0 << 8, src.y0 << 8, mp_rect_w(src) << 8, mp_rect_h(src) << 8); @@ -528,15 +528,18 @@ static void resize(struct vo *vo) vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res); int window_w = p->screen_osd_res.ml + p->screen_osd_res.mr + mp_rect_w(dst); int window_h = p->screen_osd_res.mt + p->screen_osd_res.mb + mp_rect_h(dst); - wp_viewport_set_destination(wl->viewport, window_w, window_h); + wp_viewport_set_destination(wl->viewport, lround(window_w / wl->scaling), + lround(window_h / wl->scaling)); //now we restore pan for video viewport calculation vo->opts->pan_x = vo_opts->pan_x; vo->opts->pan_y = vo_opts->pan_y; vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res); - wp_viewport_set_destination(wl->video_viewport, mp_rect_w(dst), mp_rect_h(dst)); + wp_viewport_set_destination(wl->video_viewport, lround(mp_rect_w(dst) / wl->scaling), + lround(mp_rect_h(dst) / wl->scaling)); wl_subsurface_set_position(wl->video_subsurface, dst.x0, dst.y0); - wp_viewport_set_destination(wl->osd_viewport, vo->dwidth, vo->dheight); + wp_viewport_set_destination(wl->osd_viewport, lround(vo->dwidth / wl->scaling), + lround(vo->dheight / wl->scaling)); wl_subsurface_set_position(wl->osd_subsurface, 0 - dst.x0, 0 - dst.y0); set_viewport_source(vo, src); } @@ -692,10 +695,7 @@ done: if (!vo_wayland_reconfig(vo)) return VO_ERROR; - // mpv rotates clockwise but the wayland spec has counter-clockwise rotations - // swap 1 and 3 to match mpv's direction - int transform = (360 - img->params.rotate) % 360 / 90; - wl_surface_set_buffer_transform(vo->wl->video_surface, transform); + wl_surface_set_buffer_transform(vo->wl->video_surface, img->params.rotate / 90); // Immediately destroy all buffers if params change. destroy_buffers(vo); @@ -781,12 +781,6 @@ static int preinit(struct vo *vo) goto err; } - if (!vo->wl->viewport) { - MP_FATAL(vo->wl, "Compositor doesn't support the %s protocol!\n", - wp_viewporter_interface.name); - goto err; - } - if (vo->wl->single_pixel_manager) { #if HAVE_WAYLAND_PROTOCOLS_1_27 p->solid_buffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c index aae73f7..34726a3 100644 --- a/video/out/vo_drm.c +++ b/video/out/vo_drm.c @@ -24,7 +24,6 @@ #include #include -#include #include "common/msg.h" #include "drm_atomic.h" @@ -38,11 +37,12 @@ #include "vo.h" #define IMGFMT_XRGB8888 IMGFMT_BGR0 -#if BYTE_ORDER == BIG_ENDIAN -#define IMGFMT_XRGB2101010 pixfmt2imgfmt(AV_PIX_FMT_GBRP10BE) -#else -#define IMGFMT_XRGB2101010 pixfmt2imgfmt(AV_PIX_FMT_GBRP10LE) -#endif +#define IMGFMT_XBGR8888 IMGFMT_RGB0 +#define IMGFMT_XRGB2101010 \ + pixfmt2imgfmt(MP_SELECT_LE_BE(AV_PIX_FMT_X2RGB10LE, AV_PIX_FMT_X2RGB10BE)) +#define IMGFMT_XBGR2101010 \ + pixfmt2imgfmt(MP_SELECT_LE_BE(AV_PIX_FMT_X2BGR10LE, AV_PIX_FMT_X2BGR10BE)) +#define IMGFMT_YUYV pixfmt2imgfmt(AV_PIX_FMT_YUYV422) #define BYTES_PER_PIXEL 4 #define BITS_PER_PIXEL 32 @@ -118,12 +118,31 @@ static struct framebuffer *setup_framebuffer(struct vo *vo) fb->handle = creq.handle; // select format - if (drm->opts->drm_format == DRM_OPTS_FORMAT_XRGB2101010) { + switch (drm->opts->drm_format) { + case DRM_OPTS_FORMAT_XRGB2101010: p->drm_format = DRM_FORMAT_XRGB2101010; p->imgfmt = IMGFMT_XRGB2101010; - } else { - p->drm_format = DRM_FORMAT_XRGB8888;; + break; + case DRM_OPTS_FORMAT_XBGR2101010: + p->drm_format = DRM_FORMAT_XRGB2101010; + p->imgfmt = IMGFMT_XRGB2101010; + break; + case DRM_OPTS_FORMAT_XBGR8888: + p->drm_format = DRM_FORMAT_XBGR8888; + p->imgfmt = IMGFMT_XBGR8888; + break; + case DRM_OPTS_FORMAT_YUYV: + p->drm_format = DRM_FORMAT_YUYV; + p->imgfmt = IMGFMT_YUYV; + break; + default: + if (drm->opts->drm_format != DRM_OPTS_FORMAT_XRGB8888) { + MP_VERBOSE(vo, "Requested format not supported by VO, " + "falling back to xrgb8888\n"); + } + p->drm_format = DRM_FORMAT_XRGB8888; p->imgfmt = IMGFMT_XRGB8888; + break; } // create framebuffer object for the dumb-buffer @@ -172,14 +191,15 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) vo->dheight = drm->fb->height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); - int w = p->dst.x1 - p->dst.x0; - int h = p->dst.y1 - p->dst.y0; + struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(p->imgfmt); + p->dst.x0 = MP_ALIGN_DOWN(p->dst.x0, fmt.align_x); + p->dst.y0 = MP_ALIGN_DOWN(p->dst.y0, fmt.align_y); p->sws->src = *params; p->sws->dst = (struct mp_image_params) { .imgfmt = p->imgfmt, - .w = w, - .h = h, + .w = mp_rect_w(p->dst), + .h = mp_rect_h(p->dst), .p_w = 1, .p_h = 1, }; @@ -200,6 +220,9 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (mp_sws_reinit(p->sws) < 0) return -1; + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &p->sws->dst; // essentially constant, so this is okay + mp_mutex_unlock(&vo->params_mutex); vo->want_redraw = true; return 0; } @@ -239,35 +262,10 @@ static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *buf) osd_draw_on_image(vo->osd, p->osd, 0, 0, p->cur_frame); } - if (p->drm_format == DRM_FORMAT_XRGB2101010) { - // Pack GBRP10 image into XRGB2101010 for DRM - const int w = p->cur_frame->w; - const int h = p->cur_frame->h; - - const int g_padding = p->cur_frame->stride[0]/sizeof(uint16_t) - w; - const int b_padding = p->cur_frame->stride[1]/sizeof(uint16_t) - w; - const int r_padding = p->cur_frame->stride[2]/sizeof(uint16_t) - w; - const int fbuf_padding = buf->stride/sizeof(uint32_t) - w; - - uint16_t *g_ptr = (uint16_t*)p->cur_frame->planes[0]; - uint16_t *b_ptr = (uint16_t*)p->cur_frame->planes[1]; - uint16_t *r_ptr = (uint16_t*)p->cur_frame->planes[2]; - uint32_t *fbuf_ptr = (uint32_t*)buf->map; - for (unsigned y = 0; y < h; ++y) { - for (unsigned x = 0; x < w; ++x) { - *fbuf_ptr++ = (*r_ptr++ << 20) | (*g_ptr++ << 10) | (*b_ptr++); - } - g_ptr += g_padding; - b_ptr += b_padding; - r_ptr += r_padding; - fbuf_ptr += fbuf_padding; - } - } else { // p->drm_format == DRM_FORMAT_XRGB8888 - memcpy_pic(buf->map, p->cur_frame->planes[0], - p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h, - buf->stride, - p->cur_frame->stride[0]); - } + memcpy_pic(buf->map, p->cur_frame->planes[0], + p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h, + buf->stride, + p->cur_frame->stride[0]); } if (mpi != p->last_input) { @@ -423,7 +421,8 @@ err: static int query_format(struct vo *vo, int format) { - return sws_isSupportedInput(imgfmt2pixfmt(format)); + struct priv *p = vo->priv; + return mp_sws_supports_formats(p->sws, p->imgfmt, format) ? 1 : 0; } static int control(struct vo *vo, uint32_t request, void *arg) diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c index c02e6e7..d49a6ba 100644 --- a/video/out/vo_gpu.c +++ b/video/out/vo_gpu.c @@ -80,11 +80,16 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) if (!sw->fns->start_frame(sw, &fbo)) return; - gl_video_render_frame(p->renderer, frame, fbo, RENDER_FRAME_DEF); + gl_video_render_frame(p->renderer, frame, &fbo, RENDER_FRAME_DEF); if (!sw->fns->submit_frame(sw, frame)) { MP_ERR(vo, "Failed presenting frame!\n"); return; } + + struct mp_image_params *params = gl_video_get_target_params_ptr(p->renderer); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = params; + mp_mutex_unlock(&vo->params_mutex); } static void flip_page(struct vo *vo) @@ -169,13 +174,14 @@ static void get_and_update_ambient_lighting(struct gpu_priv *p) } } -static void update_ra_ctx_options(struct vo *vo) +static void update_ra_ctx_options(struct vo *vo, struct ra_ctx_opts *ctx_opts) { struct gpu_priv *p = vo->priv; - - /* Only the alpha option has any runtime toggle ability. */ struct gl_video_opts *gl_opts = mp_get_config_group(p->ctx, vo->global, &gl_video_conf); - p->ctx->opts.want_alpha = gl_opts->alpha_mode == 1; + ctx_opts->want_alpha = (gl_opts->background == BACKGROUND_COLOR && + gl_opts->background_color.a != 255) || + gl_opts->background == BACKGROUND_NONE; + talloc_free(gl_opts); } static int control(struct vo *vo, uint32_t request, void *data) @@ -186,9 +192,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_SET_PANSCAN: resize(vo); return VO_TRUE; - case VOCTRL_SET_EQUALIZER: - vo->want_redraw = true; - return VO_TRUE; case VOCTRL_SCREENSHOT: { struct vo_frame *frame = vo_get_current_vo_frame(vo); if (frame) @@ -200,12 +203,14 @@ static int control(struct vo *vo, uint32_t request, void *data) request_hwdec_api(vo, data); return true; case VOCTRL_UPDATE_RENDER_OPTS: { - update_ra_ctx_options(vo); + struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf); + update_ra_ctx_options(vo, ctx_opts); gl_video_configure_queue(p->renderer, vo); get_and_update_icc_profile(p); if (p->ctx->fns->update_render_opts) p->ctx->fns->update_render_opts(p->ctx); vo->want_redraw = true; + talloc_free(ctx_opts); return true; } case VOCTRL_RESET: @@ -288,12 +293,9 @@ static int preinit(struct vo *vo) p->log = vo->log; struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf); - struct gl_video_opts *gl_opts = mp_get_config_group(vo, vo->global, &gl_video_conf); - struct ra_ctx_opts opts = *ctx_opts; - opts.want_alpha = gl_opts->alpha_mode == 1; - p->ctx = ra_ctx_create(vo, opts); + update_ra_ctx_options(vo, ctx_opts); + p->ctx = ra_ctx_create(vo, *ctx_opts); talloc_free(ctx_opts); - talloc_free(gl_opts); if (!p->ctx) goto err_out; assert(p->ctx->ra); diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 1dc1b18..0a93f63 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -17,6 +17,9 @@ * License along with mpv. If not, see . */ +#include +#include +#include #include #include @@ -29,7 +32,9 @@ #include "config.h" #include "common/common.h" +#include "misc/io_utils.h" #include "options/m_config.h" +#include "options/options.h" #include "options/path.h" #include "osdep/io.h" #include "osdep/threads.h" @@ -90,9 +95,12 @@ struct frame_info { }; struct cache { - char *path; + struct mp_log *log; + struct mpv_global *global; + char *dir; + const char *name; + size_t size_limit; pl_cache cache; - uint64_t sig; }; struct priv { @@ -131,21 +139,18 @@ struct priv { pl_options pars; struct m_config_cache *opts_cache; + struct m_config_cache *next_opts_cache; + struct gl_next_opts *next_opts; struct cache shader_cache, icc_cache; struct mp_csp_equalizer_state *video_eq; struct scaler_params scalers[SCALER_COUNT]; const struct pl_hook **hooks; // storage for `params.hooks` - enum mp_csp_levels output_levels; - char **raw_opts; + enum pl_color_levels output_levels; struct pl_icc_params icc_params; char *icc_path; pl_icc_object icc_profile; - struct user_lut image_lut; - struct user_lut target_lut; - struct user_lut lut; - // Cached shaders, preserved across options updates struct user_hook *user_hooks; int num_user_hooks; @@ -154,15 +159,59 @@ struct priv { struct frame_info perf_fresh; struct frame_info perf_redraw; + struct mp_image_params target_params; +}; + +static void update_render_options(struct vo *vo); +static void update_lut(struct priv *p, struct user_lut *lut); + +struct gl_next_opts { bool delayed_peak; + int border_background; + float corner_rounding; bool inter_preserve; + struct user_lut lut; + struct user_lut image_lut; + struct user_lut target_lut; bool target_hint; + char **raw_opts; +}; - float corner_rounding; +const struct m_opt_choice_alternatives lut_types[] = { + {"auto", PL_LUT_UNKNOWN}, + {"native", PL_LUT_NATIVE}, + {"normalized", PL_LUT_NORMALIZED}, + {"conversion", PL_LUT_CONVERSION}, + {0} }; -static void update_render_options(struct vo *vo); -static void update_lut(struct priv *p, struct user_lut *lut); +#define OPT_BASE_STRUCT struct gl_next_opts +const struct m_sub_options gl_next_conf = { + .opts = (const struct m_option[]) { + {"allow-delayed-peak-detect", OPT_BOOL(delayed_peak)}, + {"border-background", OPT_CHOICE(border_background, + {"none", BACKGROUND_NONE}, + {"color", BACKGROUND_COLOR}, + {"tiles", BACKGROUND_TILES})}, + {"corner-rounding", OPT_FLOAT(corner_rounding), M_RANGE(0, 1)}, + {"interpolation-preserve", OPT_BOOL(inter_preserve)}, + {"lut", OPT_STRING(lut.opt), .flags = M_OPT_FILE}, + {"lut-type", OPT_CHOICE_C(lut.type, lut_types)}, + {"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE}, + {"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)}, + {"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE}, + {"target-colorspace-hint", OPT_BOOL(target_hint)}, + // No `target-lut-type` because we don't support non-RGB targets + {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)}, + {0}, + }, + .defaults = &(struct gl_next_opts) { + .border_background = BACKGROUND_COLOR, + .inter_preserve = true, + }, + .size = sizeof(struct gl_next_opts), + .change_flags = UPDATE_VIDEO, +}; static pl_buf get_dr_buf(struct priv *p, const uint8_t *ptr) { @@ -238,8 +287,6 @@ static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h, return mpi; } -static struct pl_color_space get_mpi_csp(struct vo *vo, struct mp_image *mpi); - static void update_overlays(struct vo *vo, struct mp_osd_res res, int flags, enum pl_overlay_coords coords, struct osd_state *state, struct pl_frame *frame, @@ -290,6 +337,8 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, entry->num_parts = 0; for (int i = 0; i < item->num_parts; i++) { const struct sub_bitmap *b = &item->parts[i]; + if (b->dw == 0 || b->dh == 0) + continue; uint32_t c = b->libass.color; struct pl_overlay_part part = { .src = { b->src_x, b->src_y, b->src_x + b->w, b->src_y + b->h }, @@ -322,7 +371,7 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, ol->repr.alpha = PL_ALPHA_PREMULTIPLIED; // Infer bitmap colorspace from source if (src) { - ol->color = get_mpi_csp(vo, src); + ol->color = src->params.color; // Seems like HDR subtitles are targeting SDR white if (pl_color_transfer_is_hdr(ol->color.transfer)) { ol->color.hdr = (struct pl_hdr_metadata) { @@ -332,6 +381,8 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, } break; case SUBBITMAP_LIBASS: + if (src && item->video_color_space && !pl_color_space_is_hdr(&src->params.color)) + ol->color = src->params.color; ol->mode = PL_OVERLAY_MONOCHROME; ol->repr.alpha = PL_ALPHA_INDEPENDENT; break; @@ -441,21 +492,15 @@ static int plane_data_from_imgfmt(struct pl_plane_data out_data[4], return desc.num_planes; } -static struct pl_color_space get_mpi_csp(struct vo *vo, struct mp_image *mpi) -{ - struct pl_color_space csp = { - .primaries = mp_prim_to_pl(mpi->params.color.primaries), - .transfer = mp_trc_to_pl(mpi->params.color.gamma), - .hdr = mpi->params.color.hdr, - }; - return csp; -} - static bool hwdec_reconfig(struct priv *p, struct ra_hwdec *hwdec, const struct mp_image_params *par) { if (p->hwdec_mapper) { - if (mp_image_params_equal(par, &p->hwdec_mapper->src_params)) { + if (mp_image_params_static_equal(par, &p->hwdec_mapper->src_params)) { + p->hwdec_mapper->src_params.repr.dovi = par->repr.dovi; + p->hwdec_mapper->dst_params.repr.dovi = par->repr.dovi; + p->hwdec_mapper->src_params.color.hdr = par->color.hdr; + p->hwdec_mapper->dst_params.color.hdr = par->color.hdr; return p->hwdec_mapper; } else { ra_hwdec_mapper_free(&p->hwdec_mapper); @@ -571,12 +616,8 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src } *frame = (struct pl_frame) { - .color = get_mpi_csp(vo, mpi), - .repr = { - .sys = mp_csp_to_pl(par->color.space), - .levels = mp_levels_to_pl(par->color.levels), - .alpha = mp_alpha_to_pl(par->alpha), - }, + .color = par->color, + .repr = par->repr, .profile = { .data = mpi->icc_profile ? mpi->icc_profile->data : NULL, .len = mpi->icc_profile ? mpi->icc_profile->size : 0, @@ -588,14 +629,14 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src // mp_image, like AVFrame, likes communicating RGB/XYZ/YCbCr status // implicitly via the image format, rather than the actual tagging. switch (mp_imgfmt_get_forced_csp(par->imgfmt)) { - case MP_CSP_RGB: + case PL_COLOR_SYSTEM_RGB: frame->repr.sys = PL_COLOR_SYSTEM_RGB; frame->repr.levels = PL_COLOR_LEVELS_FULL; break; - case MP_CSP_XYZ: + case PL_COLOR_SYSTEM_XYZ: frame->repr.sys = PL_COLOR_SYSTEM_XYZ; break; - case MP_CSP_AUTO: + case PL_COLOR_SYSTEM_UNKNOWN: if (!frame->repr.sys) frame->repr.sys = pl_color_system_guess_ycbcr(par->w, par->h); break; @@ -664,10 +705,7 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src } // Update chroma location, must be done after initializing planes - pl_frame_set_chroma_location(frame, mp_chroma_to_pl(par->chroma_location)); - - // Set the frame DOVI metadata - mp_map_dovi_metadata_to_pl(mpi, frame); + pl_frame_set_chroma_location(frame, par->chroma_location); if (mpi->film_grain) pl_film_grain_from_av(&frame->film_grain, (AVFilmGrainParams *) mpi->film_grain->data); @@ -679,9 +717,9 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src pl_icc_profile_compute_signature(&frame->profile); // Update LUT attached to this frame - update_lut(p, &p->image_lut); - frame->lut = p->image_lut.lut; - frame->lut_type = p->image_lut.type; + update_lut(p, &p->next_opts->image_lut); + frame->lut = p->next_opts->image_lut.lut; + frame->lut_type = p->next_opts->image_lut.type; return true; } @@ -727,12 +765,14 @@ static void update_options(struct vo *vo) { struct priv *p = vo->priv; pl_options pars = p->pars; - if (m_config_cache_update(p->opts_cache)) + bool changed = m_config_cache_update(p->opts_cache); + changed = m_config_cache_update(p->next_opts_cache) || changed; + if (changed) update_render_options(vo); - update_lut(p, &p->lut); - pars->params.lut = p->lut.lut; - pars->params.lut_type = p->lut.type; + update_lut(p, &p->next_opts->lut); + pars->params.lut = p->next_opts->lut.lut; + pars->params.lut_type = p->next_opts->lut.type; // Update equalizer state struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; @@ -744,7 +784,7 @@ static void update_options(struct vo *vo) pars->color_adjustment.gamma = cparams.gamma; p->output_levels = cparams.levels_out; - for (char **kv = p->raw_opts; kv && kv[0]; kv += 2) + for (char **kv = p->next_opts->raw_opts; kv && kv[0]; kv += 2) pl_options_set_str(pars, kv[0], kv[1]); } @@ -776,18 +816,18 @@ static void apply_target_contrast(struct priv *p, struct pl_color_space *color) static void apply_target_options(struct priv *p, struct pl_frame *target) { - update_lut(p, &p->target_lut); - target->lut = p->target_lut.lut; - target->lut_type = p->target_lut.type; + update_lut(p, &p->next_opts->target_lut); + target->lut = p->next_opts->target_lut.lut; + target->lut_type = p->next_opts->target_lut.type; // Colorspace overrides const struct gl_video_opts *opts = p->opts_cache->opts; if (p->output_levels) - target->repr.levels = mp_levels_to_pl(p->output_levels); + target->repr.levels = p->output_levels; if (opts->target_prim) - target->color.primaries = mp_prim_to_pl(opts->target_prim); + target->color.primaries = opts->target_prim; if (opts->target_trc) - target->color.transfer = mp_trc_to_pl(opts->target_trc); + target->color.transfer = opts->target_trc; // If swapchain returned a value use this, override is used in hint if (opts->target_peak && !target->color.hdr.max_luma) target->color.hdr.max_luma = opts->target_peak; @@ -796,14 +836,23 @@ static void apply_target_options(struct priv *p, struct pl_frame *target) if (opts->target_gamut) { // Ensure resulting gamut still fits inside container const struct pl_raw_primaries *gamut, *container; - gamut = pl_raw_primaries_get(mp_prim_to_pl(opts->target_gamut)); + gamut = pl_raw_primaries_get(opts->target_gamut); container = pl_raw_primaries_get(target->color.primaries); target->color.hdr.prim = pl_primaries_clip(gamut, container); } - if (opts->dither_depth > 0) { + int dither_depth = opts->dither_depth; + if (dither_depth == 0) { + struct ra_swapchain *sw = p->ra_ctx->swapchain; + if (sw->fns->color_depth) { + dither_depth = sw->fns->color_depth(sw); + } else if (!pl_color_transfer_is_hdr(target->color.transfer)) { + dither_depth = 8; + } + } + if (dither_depth > 0) { struct pl_bit_encoding *tbits = &target->repr.bits; - tbits->color_depth += opts->dither_depth - tbits->sample_depth; - tbits->sample_depth = opts->dither_depth; + tbits->color_depth += dither_depth - tbits->sample_depth; + tbits->sample_depth = dither_depth; } if (opts->icc_opts->icc_use_luma) { @@ -882,7 +931,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) params.info_callback = info_callback; params.info_priv = vo; params.skip_caching_single_frame = !cache_frame; - params.preserve_mixing_cache = p->inter_preserve && !frame->still; + params.preserve_mixing_cache = p->next_opts->inter_preserve && !frame->still; if (frame->still) params.frame_mixer = NULL; @@ -939,17 +988,17 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) p->last_id = id; } - if (p->target_hint && frame->current) { - struct pl_color_space hint = get_mpi_csp(vo, frame->current); + if (p->next_opts->target_hint && frame->current) { + struct pl_color_space hint = frame->current->params.color; if (opts->target_prim) - hint.primaries = mp_prim_to_pl(opts->target_prim); + hint.primaries = opts->target_prim; if (opts->target_trc) - hint.transfer = mp_trc_to_pl(opts->target_trc); + hint.transfer = opts->target_trc; if (opts->target_peak) hint.hdr.max_luma = opts->target_peak; apply_target_contrast(p, &hint); pl_swapchain_colorspace_hint(p->sw, &hint); - } else if (!p->target_hint) { + } else if (!p->next_opts->target_hint) { pl_swapchain_colorspace_hint(p->sw, NULL); } @@ -959,14 +1008,15 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) { if (frame->current) { // Advance the queue state to the current PTS to discard unused frames - pl_queue_update(p->queue, NULL, pl_queue_params( + struct pl_queue_params qparams = *pl_queue_params( .pts = frame->current->pts + pts_offset, .radius = pl_frame_mix_radius(¶ms), .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0, + ); #if PL_API_VER >= 340 - .drift_compensation = 0, + qparams.drift_compensation = 0; #endif - )); + pl_queue_update(p->queue, NULL, &qparams); } return; } @@ -992,10 +1042,10 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) .radius = pl_frame_mix_radius(¶ms), .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0, .interpolation_threshold = opts->interpolation_threshold, + ); #if PL_API_VER >= 340 - .drift_compensation = 0, + qparams.drift_compensation = 0; #endif - ); // Depending on the vsync ratio, we may be up to half of the vsync // duration before the current frame time. This works fine because @@ -1035,7 +1085,9 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) struct frame_priv *fp = mpi->priv; apply_crop(image, p->src, vo->params->w, vo->params->h); if (opts->blend_subs) { - if (frame->redraw || fp->osd_sync < p->osd_sync) { + if (frame->redraw) + p->osd_sync++; + if (fp->osd_sync < p->osd_sync) { float rx = pl_rect_w(p->dst) / pl_rect_w(image->crop); float ry = pl_rect_h(p->dst) / pl_rect_h(image->crop); struct mp_osd_res res = { @@ -1047,9 +1099,6 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) .mb = (image->crop.y1 - vo->params->h) * ry, .display_par = 1.0, }; - // TODO: fix this doing pointless updates - if (frame->redraw) - p->osd_sync++; update_overlays(vo, res, OSD_DRAW_SUB_ONLY, PL_OVERLAY_COORDS_DST_CROP, &fp->subs, image, mpi); @@ -1078,12 +1127,27 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) goto done; } - const struct pl_frame *cur_frame = pl_frame_mix_nearest(&mix); - if (cur_frame && vo->params) { - vo->params->color.hdr = cur_frame->color.hdr; + struct pl_frame ref_frame; + pl_frames_infer_mix(p->rr, &mix, &target, &ref_frame); + + mp_mutex_lock(&vo->params_mutex); + p->target_params = (struct mp_image_params){ + .imgfmt_name = swframe.fbo->params.format + ? swframe.fbo->params.format->name : NULL, + .w = mp_rect_w(p->dst), + .h = mp_rect_h(p->dst), + .color = target.color, + .repr = target.repr, + .rotate = target.rotation, + }; + vo->target_params = &p->target_params; + + if (vo->params) { + vo->params->color.hdr = ref_frame.color.hdr; // Augment metadata with peak detection max_pq_y / avg_pq_y pl_renderer_get_hdr_metadata(p->rr, &vo->params->color.hdr); } + mp_mutex_unlock(&vo->params_mutex); p->is_interpolated = pts_offset != 0 && mix.num_frames > 1; valid = true; @@ -1168,6 +1232,9 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) return -1; resize(vo); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = NULL; + mp_mutex_unlock(&vo->params_mutex); return 0; } @@ -1236,12 +1303,13 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args) // Retrieve the current frame from the frame queue struct pl_frame_mix mix; enum pl_queue_status status; - status = pl_queue_update(p->queue, &mix, pl_queue_params( + struct pl_queue_params qparams = *pl_queue_params( .pts = p->last_pts, + ); #if PL_API_VER >= 340 - .drift_compensation = 0, + qparams.drift_compensation = 0; #endif - )); + status = pl_queue_update(p->queue, &mix, &qparams); assert(status != PL_QUEUE_EOF); if (status == PL_QUEUE_ERR) { MP_ERR(vo, "Unknown error occurred while trying to take screenshot!\n"); @@ -1379,9 +1447,9 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args) if (!args->res) goto done; - args->res->params.color.primaries = mp_prim_from_pl(target.color.primaries); - args->res->params.color.gamma = mp_trc_from_pl(target.color.transfer); - args->res->params.color.levels = mp_levels_from_pl(target.repr.levels); + args->res->params.color.primaries = target.color.primaries; + args->res->params.color.transfer = target.color.transfer; + args->res->params.repr.levels = target.repr.levels; args->res->params.color.hdr = target.color.hdr; if (args->scaled) args->res->params.p_w = args->res->params.p_h = 1; @@ -1424,6 +1492,19 @@ static inline void copy_frame_info_to_mp(struct frame_info *pl, } } +static void update_ra_ctx_options(struct vo *vo, struct ra_ctx_opts *ctx_opts) +{ + struct priv *p = vo->priv; + struct gl_video_opts *gl_opts = p->opts_cache->opts; + bool border_alpha = (p->next_opts->border_background == BACKGROUND_COLOR && + gl_opts->background_color.a != 255) || + p->next_opts->border_background == BACKGROUND_NONE; + ctx_opts->want_alpha = (gl_opts->background == BACKGROUND_COLOR && + gl_opts->background_color.a != 255) || + gl_opts->background == BACKGROUND_NONE || + border_alpha; +} + static int control(struct vo *vo, uint32_t request, void *data) { struct priv *p = vo->priv; @@ -1432,7 +1513,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_SET_PANSCAN: resize(vo); return VO_TRUE; - case VOCTRL_SET_EQUALIZER: case VOCTRL_PAUSE: if (p->is_interpolated) vo->want_redraw = true; @@ -1440,8 +1520,7 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_UPDATE_RENDER_OPTS: { m_config_cache_update(p->opts_cache); - const struct gl_video_opts *opts = p->opts_cache->opts; - p->ra_ctx->opts.want_alpha = opts->alpha_mode == ALPHA_YES; + update_ra_ctx_options(vo, &p->ra_ctx->opts); if (p->ra_ctx->fns->update_render_opts) p->ra_ctx->fns->update_render_opts(p->ra_ctx); update_render_options(vo); @@ -1513,84 +1592,190 @@ static void wait_events(struct vo *vo, int64_t until_time_ns) } } -#if PL_API_VER < 342 -static inline void xor_hash(void *hash, pl_cache_obj obj) +static char *cache_filepath(void *ta_ctx, char *dir, const char *prefix, uint64_t key) { - *((uint64_t *) hash) ^= obj.key; + bstr filename = {0}; + bstr_xappend_asprintf(ta_ctx, &filename, "%s_%016" PRIx64, prefix, key); + return mp_path_join_bstr(ta_ctx, bstr0(dir), filename); } -static inline uint64_t pl_cache_signature(pl_cache cache) +static pl_cache_obj cache_load_obj(void *p, uint64_t key) { - uint64_t hash = 0; - pl_cache_iterate(cache, xor_hash, &hash); - return hash; + struct cache *c = p; + void *ta_ctx = talloc_new(NULL); + pl_cache_obj obj = {0}; + + if (!c->dir) + goto done; + + char *filepath = cache_filepath(ta_ctx, c->dir, c->name, key); + if (!filepath) + goto done; + + if (stat(filepath, &(struct stat){0})) + goto done; + + int64_t load_start = mp_time_ns(); + struct bstr data = stream_read_file(filepath, ta_ctx, c->global, STREAM_MAX_READ_SIZE); + int64_t load_end = mp_time_ns(); + MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), load time(%.3f ms)\n", + __func__, key, data.len, + MP_TIME_NS_TO_MS(load_end - load_start)); + + obj = (pl_cache_obj){ + .key = key, + .data = talloc_steal(NULL, data.start), + .size = data.len, + .free = talloc_free, + }; + +done: + talloc_free(ta_ctx); + return obj; +} + +static void cache_save_obj(void *p, pl_cache_obj obj) +{ + if (!obj.data || !obj.size) + return; + + const struct cache *c = p; + void *ta_ctx = talloc_new(NULL); + + if (!c->dir) + goto done; + + char *filepath = cache_filepath(ta_ctx, c->dir, c->name, obj.key); + if (!filepath) + goto done; + + // Don't save if already exists + if (!stat(filepath, &(struct stat){0})) { + MP_DBG(c, "%s: key(%"PRIx64"), size(%zu)\n", __func__, obj.key, obj.size); + goto done; + } + + int64_t save_start = mp_time_ns(); + mp_save_to_file(filepath, obj.data, obj.size); + int64_t save_end = mp_time_ns(); + MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), save time(%.3f ms)\n", + __func__, obj.key, obj.size, + MP_TIME_NS_TO_MS(save_end - save_start)); + +done: + talloc_free(ta_ctx); } -#endif static void cache_init(struct vo *vo, struct cache *cache, size_t max_size, const char *dir_opt) { struct priv *p = vo->priv; - const char *name = cache == &p->shader_cache ? "shader.cache" : "icc.cache"; + const char *name = cache == &p->shader_cache ? "shader" : "icc"; + const size_t limit = cache == &p->shader_cache ? 128 << 20 : 1536 << 20; char *dir; if (dir_opt && dir_opt[0]) { - dir = mp_get_user_path(NULL, p->global, dir_opt); + dir = mp_get_user_path(vo, p->global, dir_opt); } else { - dir = mp_find_user_file(NULL, p->global, "cache", ""); + dir = mp_find_user_file(vo, p->global, "cache", ""); } if (!dir || !dir[0]) - goto done; + return; mp_mkdirp(dir); - cache->path = mp_path_join(vo, dir, name); - cache->cache = pl_cache_create(pl_cache_params( - .log = p->pllog, - .max_total_size = max_size, - )); + *cache = (struct cache){ + .log = p->log, + .global = p->global, + .dir = dir, + .name = name, + .size_limit = limit, + .cache = pl_cache_create(pl_cache_params( + .log = p->pllog, + .get = cache_load_obj, + .set = cache_save_obj, + .priv = cache + )), + }; +} - FILE *file = fopen(cache->path, "rb"); - if (file) { - int ret = pl_cache_load_file(cache->cache, file); - fclose(file); - if (ret < 0) - MP_WARN(p, "Failed loading cache from %s\n", cache->path); - } +struct file_entry { + char *filepath; + size_t size; + time_t atime; +}; - cache->sig = pl_cache_signature(cache->cache); -done: - talloc_free(dir); +static int compare_atime(const void *a, const void *b) +{ + return (((struct file_entry *)b)->atime - ((struct file_entry *)a)->atime); } static void cache_uninit(struct priv *p, struct cache *cache) { if (!cache->cache) - goto done; - if (pl_cache_signature(cache->cache) == cache->sig) - goto done; // skip re-saving identical cache + return; - assert(cache->path); - char *tmp = talloc_asprintf(cache->path, "%sXXXXXX", cache->path); - int fd = mkstemp(tmp); - if (fd < 0) - goto done; - FILE *file = fdopen(fd, "wb"); - if (!file) { - close(fd); - unlink(tmp); + void *ta_ctx = talloc_new(NULL); + struct file_entry *files = NULL; + size_t num_files = 0; + assert(cache->dir); + assert(cache->name); + + DIR *d = opendir(cache->dir); + if (!d) goto done; + + struct dirent *dir; + while ((dir = readdir(d)) != NULL) { + char *filepath = mp_path_join(ta_ctx, cache->dir, dir->d_name); + if (!filepath) + continue; + struct stat filestat; + if (stat(filepath, &filestat)) + continue; + if (!S_ISREG(filestat.st_mode)) + continue; + bstr fname = bstr0(dir->d_name); + if (!bstr_eatstart0(&fname, cache->name)) + continue; + if (!bstr_eatstart0(&fname, "_")) + continue; + if (fname.len != 16) // %016x + continue; + MP_TARRAY_APPEND(ta_ctx, files, num_files, + (struct file_entry){ + .filepath = filepath, + .size = filestat.st_size, + .atime = filestat.st_atime, + }); } - int ret = pl_cache_save_file(cache->cache, file); - fclose(file); - if (ret >= 0) - ret = rename(tmp, cache->path); - if (ret < 0) { - MP_WARN(p, "Failed saving cache to %s\n", cache->path); - unlink(tmp); + closedir(d); + + if (!num_files) + goto done; + + qsort(files, num_files, sizeof(struct file_entry), compare_atime); + + time_t t = time(NULL); + size_t cache_size = 0; + size_t cache_limit = cache->size_limit ? cache->size_limit : SIZE_MAX; + for (int i = 0; i < num_files; i++) { + // Remove files that exceed the size limit but are older than one day. + // This allows for temporary maintaining a larger cache size while + // adjusting the configuration. The cache will be cleared the next day + // for unused entries. We don't need to be overly aggressive with cache + // cleaning; in most cases, it will not grow much, and in others, it may + // actually be useful to cache more. + cache_size += files[i].size; + double rel_use = difftime(t, files[i].atime); + if (cache_size > cache_limit && rel_use > 60 * 60 * 24) { + MP_VERBOSE(p, "Removing %s | size: %9zu bytes | last used: %9d seconds ago\n", + files[i].filepath, files[i].size, (int)rel_use); + unlink(files[i].filepath); + } } - // fall through done: + talloc_free(ta_ctx); pl_cache_destroy(&cache->cache); } @@ -1618,6 +1803,10 @@ static void uninit(struct vo *vo) cache_uninit(p, &p->shader_cache); cache_uninit(p, &p->icc_cache); + pl_lut_free(&p->next_opts->image_lut.lut); + pl_lut_free(&p->next_opts->lut.lut); + pl_lut_free(&p->next_opts->target_lut.lut); + pl_icc_close(&p->icc_profile); pl_renderer_destroy(&p->rr); @@ -1644,12 +1833,17 @@ static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->opts_cache = m_config_cache_alloc(p, vo->global, &gl_video_conf); + p->next_opts_cache = m_config_cache_alloc(p, vo->global, &gl_next_conf); + p->next_opts = p->next_opts_cache->opts; p->video_eq = mp_csp_equalizer_create(p, vo->global); p->global = vo->global; p->log = vo->log; struct gl_video_opts *gl_opts = p->opts_cache->opts; - p->context = gpu_ctx_create(vo, gl_opts); + struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf); + update_ra_ctx_options(vo, ctx_opts); + p->context = gpu_ctx_create(vo, ctx_opts); + talloc_free(ctx_opts); if (!p->context) goto err_out; // For the time being @@ -1857,6 +2051,7 @@ static void update_lut(struct priv *p, struct user_lut *lut) MP_VERBOSE(p, "Loading custom LUT '%s'\n", fname); struct bstr lutdata = stream_read_file(fname, p, p->global, 100000000); // 100 MB lut->lut = pl_lut_parse_cube(p->pllog, lutdata.start, lutdata.len); + talloc_free(fname); talloc_free(lutdata.start); } @@ -1934,15 +2129,27 @@ static void update_render_options(struct vo *vo) pl_options pars = p->pars; const struct gl_video_opts *opts = p->opts_cache->opts; pars->params.antiringing_strength = opts->scaler[0].antiring; - pars->params.background_color[0] = opts->background.r / 255.0; - pars->params.background_color[1] = opts->background.g / 255.0; - pars->params.background_color[2] = opts->background.b / 255.0; - pars->params.background_transparency = 1.0 - opts->background.a / 255.0; + pars->params.background_color[0] = opts->background_color.r / 255.0; + pars->params.background_color[1] = opts->background_color.g / 255.0; + pars->params.background_color[2] = opts->background_color.b / 255.0; + pars->params.background_transparency = 1 - opts->background_color.a / 255.0; pars->params.skip_anti_aliasing = !opts->correct_downscaling; pars->params.disable_linear_scaling = !opts->linear_downscaling && !opts->linear_upscaling; pars->params.disable_fbos = opts->dumb_mode == 1; - pars->params.blend_against_tiles = opts->alpha_mode == ALPHA_BLEND_TILES; - pars->params.corner_rounding = p->corner_rounding; + +#if PL_API_VER >= 346 + int map_background_types[3] = { + PL_CLEAR_SKIP, // BACKGROUND_NONE + PL_CLEAR_COLOR, // BACKGROUND_COLOR + PL_CLEAR_TILES, // BACKGROUND_TILES + }; + pars->params.background = map_background_types[opts->background]; + pars->params.border = map_background_types[p->next_opts->border_background]; +#else + pars->params.blend_against_tiles = opts->background == BACKGROUND_TILES; +#endif + + pars->params.corner_rounding = p->next_opts->corner_rounding; pars->params.correct_subpixel_offsets = !opts->scaler_resizes_only; // Map scaler options as best we can @@ -1976,7 +2183,7 @@ static void update_render_options(struct vo *vo) pars->peak_detect_params.scene_threshold_low = opts->tone_map.scene_threshold_low; pars->peak_detect_params.scene_threshold_high = opts->tone_map.scene_threshold_high; pars->peak_detect_params.percentile = opts->tone_map.peak_percentile; - pars->peak_detect_params.allow_delayed = p->delayed_peak; + pars->peak_detect_params.allow_delayed = p->next_opts->delayed_peak; const struct pl_tone_map_function * const tone_map_funs[] = { [TONE_MAPPING_AUTO] = &pl_tone_map_auto, @@ -2055,16 +2262,6 @@ static void update_render_options(struct vo *vo) pars->params.hooks = p->hooks; } -#define OPT_BASE_STRUCT struct priv - -const struct m_opt_choice_alternatives lut_types[] = { - {"auto", PL_LUT_UNKNOWN}, - {"native", PL_LUT_NATIVE}, - {"normalized", PL_LUT_NORMALIZED}, - {"conversion", PL_LUT_CONVERSION}, - {0} -}; - const struct vo_driver video_out_gpu_next = { .description = "Video output based on libplacebo", .name = "gpu-next", @@ -2083,22 +2280,4 @@ const struct vo_driver video_out_gpu_next = { .wakeup = wakeup, .uninit = uninit, .priv_size = sizeof(struct priv), - .priv_defaults = &(const struct priv) { - .inter_preserve = true, - }, - - .options = (const struct m_option[]) { - {"allow-delayed-peak-detect", OPT_BOOL(delayed_peak)}, - {"corner-rounding", OPT_FLOAT(corner_rounding), M_RANGE(0, 1)}, - {"interpolation-preserve", OPT_BOOL(inter_preserve)}, - {"lut", OPT_STRING(lut.opt), .flags = M_OPT_FILE}, - {"lut-type", OPT_CHOICE_C(lut.type, lut_types)}, - {"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE}, - {"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)}, - {"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE}, - {"target-colorspace-hint", OPT_BOOL(target_hint)}, - // No `target-lut-type` because we don't support non-RGB targets - {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)}, - {0} - }, }; diff --git a/video/out/vo_image.c b/video/out/vo_image.c index cc48ab3..84e164f 100644 --- a/video/out/vo_image.c +++ b/video/out/vo_image.c @@ -118,7 +118,7 @@ static void flip_page(struct vo *vo) filename = mp_path_join(t, p->opts->outdir, filename); MP_INFO(vo, "Saving %s\n", filename); - write_image(p->current, p->opts->opts, filename, vo->global, vo->log); + write_image(p->current, p->opts->opts, filename, vo->global, vo->log, true); talloc_free(t); } diff --git a/video/out/vo_kitty.c b/video/out/vo_kitty.c index 7d548c7..f8f0b07 100644 --- a/video/out/vo_kitty.c +++ b/video/out/vo_kitty.c @@ -340,8 +340,9 @@ static int preinit(struct vo *vo) mp_sws_enable_cmdline_opts(p->sws, vo->global); #if HAVE_POSIX - struct sigaction sa; - sa.sa_handler = handle_winch; + struct sigaction sa = { + .sa_handler = handle_winch, + }; sigaction(SIGWINCH, &sa, &saved_sigaction); #endif diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c index 7170c1d..2842335 100644 --- a/video/out/vo_lavc.c +++ b/video/out/vo_lavc.c @@ -23,6 +23,8 @@ #include #include +#include + #include "common/common.h" #include "options/options.h" #include "video/fmt-conversion.h" @@ -112,8 +114,8 @@ static int reconfig2(struct vo *vo, struct mp_image *img) encoder->width = width; encoder->height = height; encoder->pix_fmt = pix_fmt; - encoder->colorspace = mp_csp_to_avcol_spc(params->color.space); - encoder->color_range = mp_csp_levels_to_avcol_range(params->color.levels); + encoder->colorspace = pl_system_to_av(params->repr.sys); + encoder->color_range = pl_levels_to_av(params->repr.levels); AVRational tb; diff --git a/video/out/vo_libmpv.c b/video/out/vo_libmpv.c index 972588e..7974eed 100644 --- a/video/out/vo_libmpv.c +++ b/video/out/vo_libmpv.c @@ -27,6 +27,10 @@ #include "libmpv.h" +#if HAVE_MACOS_COCOA_CB +#include "osdep/mac/app_bridge.h" +#endif + /* * mpv_render_context is managed by the host application - the host application * can access it any time, even if the VO is destroyed (or not created yet). @@ -608,9 +612,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_PAUSE: vo->want_redraw = true; return VO_TRUE; - case VOCTRL_SET_EQUALIZER: - vo->want_redraw = true; - return VO_TRUE; case VOCTRL_SET_PANSCAN: mp_mutex_lock(&ctx->lock); ctx->need_resize = true; @@ -708,6 +709,13 @@ static void uninit(struct vo *vo) static int preinit(struct vo *vo) { +#if HAVE_MACOS_COCOA_CB + cocoa_init_cocoa_cb(); +#else + if (vo->probing) + return -1; +#endif + struct vo_priv *p = vo->priv; struct mpv_render_context *ctx = diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c deleted file mode 100644 index 55f1a68..0000000 --- a/video/out/vo_rpi.c +++ /dev/null @@ -1,938 +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 . - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include "common/common.h" -#include "common/msg.h" -#include "opengl/common.h" -#include "options/m_config.h" -#include "osdep/timer.h" -#include "vo.h" -#include "win_state.h" -#include "video/mp_image.h" -#include "sub/osd.h" - -#include "opengl/ra_gl.h" -#include "gpu/video.h" - -struct mp_egl_rpi { - struct mp_log *log; - struct GL *gl; - struct ra *ra; - EGLDisplay egl_display; - EGLConfig egl_config; - EGLContext egl_context; - EGLSurface egl_surface; - // yep, the API keeps a pointer to it - EGL_DISPMANX_WINDOW_T egl_window; -}; - -struct priv { - DISPMANX_DISPLAY_HANDLE_T display; - DISPMANX_ELEMENT_HANDLE_T window; - DISPMANX_ELEMENT_HANDLE_T osd_overlay; - DISPMANX_UPDATE_HANDLE_T update; - uint32_t w, h; - uint32_t x, y; - double display_fps; - - double osd_pts; - struct mp_osd_res osd_res; - struct m_config_cache *opts_cache; - - struct mp_egl_rpi egl; - struct gl_video *gl_video; - struct mpgl_osd *osd; - - MMAL_COMPONENT_T *renderer; - bool renderer_enabled; - - bool display_synced, skip_osd; - struct mp_image *next_image; - - // for RAM input - MMAL_POOL_T *swpool; - - mp_mutex display_mutex; - mp_cond display_cond; - int64_t vsync_counter; - bool reload_display; - - int background_layer; - int video_layer; - int osd_layer; - - int display_nr; - int layer; - bool background; - bool enable_osd; -}; - -// Magic alignments (in pixels) expected by the MMAL internals. -#define ALIGN_W 32 -#define ALIGN_H 16 - -static void recreate_renderer(struct vo *vo); - -static void *get_proc_address(const GLubyte *name) -{ - void *p = eglGetProcAddress(name); - // EGL 1.4 (supported by the RPI firmware) does not necessarily return - // function pointers for core functions. - if (!p) { - void *h = dlopen("/opt/vc/lib/libbrcmGLESv2.so", RTLD_LAZY); - if (h) { - p = dlsym(h, name); - dlclose(h); - } - } - return p; -} - -static EGLConfig select_fb_config_egl(struct mp_egl_rpi *p) -{ - EGLint attributes[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - EGLint config_count; - EGLConfig config; - - eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count); - - if (!config_count) { - MP_FATAL(p, "Could find EGL configuration!\n"); - return NULL; - } - - return config; -} - -static void mp_egl_rpi_destroy(struct mp_egl_rpi *p) -{ - if (p->egl_display) { - eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - } - if (p->egl_surface) - eglDestroySurface(p->egl_display, p->egl_surface); - if (p->egl_context) - eglDestroyContext(p->egl_display, p->egl_context); - p->egl_context = EGL_NO_CONTEXT; - eglReleaseThread(); - p->egl_display = EGL_NO_DISPLAY; - talloc_free(p->gl); - p->gl = NULL; -} - -static int mp_egl_rpi_init(struct mp_egl_rpi *p, DISPMANX_ELEMENT_HANDLE_T window, - int w, int h) -{ - p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (!eglInitialize(p->egl_display, NULL, NULL)) { - MP_FATAL(p, "EGL failed to initialize.\n"); - goto fail; - } - - eglBindAPI(EGL_OPENGL_ES_API); - - EGLConfig config = select_fb_config_egl(p); - if (!config) - goto fail; - - p->egl_window = (EGL_DISPMANX_WINDOW_T){ - .element = window, - .width = w, - .height = h, - }; - p->egl_surface = eglCreateWindowSurface(p->egl_display, config, - &p->egl_window, NULL); - - if (p->egl_surface == EGL_NO_SURFACE) { - MP_FATAL(p, "Could not create EGL surface!\n"); - goto fail; - } - - EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - p->egl_context = eglCreateContext(p->egl_display, config, - EGL_NO_CONTEXT, context_attributes); - - if (p->egl_context == EGL_NO_CONTEXT) { - MP_FATAL(p, "Could not create EGL context!\n"); - goto fail; - } - - eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, - p->egl_context); - - p->gl = talloc_zero(NULL, struct GL); - - const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); - mpgl_load_functions(p->gl, get_proc_address, exts, p->log); - - if (!p->gl->version && !p->gl->es) - goto fail; - - p->ra = ra_create_gl(p->gl, p->log); - if (!p->ra) - goto fail; - - return 0; - -fail: - mp_egl_rpi_destroy(p); - return -1; -} - -// Make mpi point to buffer, assuming MMAL_ENCODING_I420. -// buffer can be NULL. -// Return the required buffer space. -static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, - struct mp_image_params *params) -{ - assert(params->imgfmt == IMGFMT_420P); - mp_image_set_params(mpi, params); - int w = MP_ALIGN_UP(params->w, ALIGN_W); - int h = MP_ALIGN_UP(params->h, ALIGN_H); - uint8_t *cur = buffer ? buffer->data : NULL; - size_t size = 0; - for (int i = 0; i < 3; i++) { - int div = i ? 2 : 1; - mpi->planes[i] = cur; - mpi->stride[i] = w / div; - size_t plane_size = h / div * mpi->stride[i]; - if (cur) - cur += plane_size; - size += plane_size; - } - return size; -} - -static void update_osd(struct vo *vo) -{ - struct priv *p = vo->priv; - if (!p->enable_osd) - return; - - if (!gl_video_check_osd_change(p->gl_video, &p->osd_res, p->osd_pts)) { - p->skip_osd = true; - return; - } - - MP_STATS(vo, "start rpi_osd"); - - struct vo_frame frame = {0}; - struct ra_fbo target = { - .tex = ra_create_wrapped_fb(p->egl.ra, 0, p->osd_res.w, p->osd_res.h), - .flip = true, - }; - gl_video_set_osd_pts(p->gl_video, p->osd_pts); - gl_video_render_frame(p->gl_video, &frame, target, RENDER_FRAME_DEF); - ra_tex_free(p->egl.ra, &target.tex); - - MP_STATS(vo, "stop rpi_osd"); -} - -static void resize(struct vo *vo) -{ - struct priv *p = vo->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - - struct mp_rect src, dst; - - vo_get_src_dst_rects(vo, &src, &dst, &p->osd_res); - - int rotate[] = {MMAL_DISPLAY_ROT0, - MMAL_DISPLAY_ROT90, - MMAL_DISPLAY_ROT180, - MMAL_DISPLAY_ROT270}; - - - int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0, - dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0; - int p_x, p_y; - av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000); - MMAL_DISPLAYREGION_T dr = { - .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION, - .size = sizeof(MMAL_DISPLAYREGION_T), }, - .src_rect = { .x = src.x0, .y = src.y0, .width = src_w, .height = src_h }, - .dest_rect = { .x = dst.x0 + p->x, .y = dst.y0 + p->y, - .width = dst_w, .height = dst_h }, - .layer = p->video_layer, - .display_num = p->display_nr, - .pixel_x = p_x, - .pixel_y = p_y, - .transform = rotate[vo->params ? vo->params->rotate / 90 : 0], - .fullscreen = vo->opts->fullscreen, - .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT | - MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM | - MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM | - MMAL_DISPLAY_SET_FULLSCREEN, - }; - - if (vo->params && (vo->params->rotate % 180) == 90) { - MPSWAP(int, dr.src_rect.x, dr.src_rect.y); - MPSWAP(int, dr.src_rect.width, dr.src_rect.height); - } - - if (mmal_port_parameter_set(input, &dr.hdr)) - MP_WARN(vo, "could not set video rectangle\n"); - - if (p->gl_video) - gl_video_resize(p->gl_video, &src, &dst, &p->osd_res); -} - -static void destroy_overlays(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (p->window) - vc_dispmanx_element_remove(p->update, p->window); - p->window = 0; - - gl_video_uninit(p->gl_video); - p->gl_video = NULL; - ra_free(&p->egl.ra); - mp_egl_rpi_destroy(&p->egl); - - if (p->osd_overlay) - vc_dispmanx_element_remove(p->update, p->osd_overlay); - p->osd_overlay = 0; -} - -static int update_display_size(struct vo *vo) -{ - struct priv *p = vo->priv; - - uint32_t n_w = 0, n_h = 0; - if (graphics_get_display_size(0, &n_w, &n_h) < 0) { - MP_FATAL(vo, "Could not get display size.\n"); - return -1; - } - - if (p->w == n_w && p->h == n_h) - return 0; - - p->w = n_w; - p->h = n_h; - - MP_VERBOSE(vo, "Display size: %dx%d\n", p->w, p->h); - - return 0; -} - -static int create_overlays(struct vo *vo) -{ - struct priv *p = vo->priv; - destroy_overlays(vo); - - if (!p->display) - return -1; - - if (vo->opts->fullscreen && p->background) { - // Use the whole screen. - VC_RECT_T dst = {.width = p->w, .height = p->h}; - VC_RECT_T src = {.width = 1 << 16, .height = 1 << 16}; - VC_DISPMANX_ALPHA_T alpha = { - .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, - .opacity = 0xFF, - }; - - p->window = vc_dispmanx_element_add(p->update, p->display, - p->background_layer, - &dst, 0, &src, - DISPMANX_PROTECTION_NONE, - &alpha, 0, 0); - if (!p->window) { - MP_FATAL(vo, "Could not add DISPMANX element.\n"); - return -1; - } - } - - if (p->enable_osd) { - VC_RECT_T dst = {.x = p->x, .y = p->y, - .width = p->osd_res.w, .height = p->osd_res.h}; - VC_RECT_T src = {.width = p->osd_res.w << 16, .height = p->osd_res.h << 16}; - VC_DISPMANX_ALPHA_T alpha = { - .flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE, - .opacity = 0xFF, - }; - p->osd_overlay = vc_dispmanx_element_add(p->update, p->display, - p->osd_layer, - &dst, 0, &src, - DISPMANX_PROTECTION_NONE, - &alpha, 0, 0); - if (!p->osd_overlay) { - MP_FATAL(vo, "Could not add DISPMANX element.\n"); - return -1; - } - - if (mp_egl_rpi_init(&p->egl, p->osd_overlay, - p->osd_res.w, p->osd_res.h) < 0) - { - MP_FATAL(vo, "EGL/GLES initialization for OSD renderer failed.\n"); - return -1; - } - p->gl_video = gl_video_init(p->egl.ra, vo->log, vo->global); - gl_video_set_clear_color(p->gl_video, (struct m_color){.a = 0}); - gl_video_set_osd_source(p->gl_video, vo->osd); - } - - p->display_fps = 0; - TV_GET_STATE_RESP_T tvstate; - TV_DISPLAY_STATE_T tvstate_disp; - if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) { - if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) { - p->display_fps = tvstate_disp.display.hdmi.frame_rate; - - HDMI_PROPERTY_PARAM_T param = { - .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE, - }; - if (!vc_tv_hdmi_get_property(¶m) && - param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC) - p->display_fps = p->display_fps / 1.001; - } else { - p->display_fps = tvstate_disp.display.sdtv.frame_rate; - } - } - - resize(vo); - - vo_event(vo, VO_EVENT_WIN_STATE); - - vc_dispmanx_update_submit_sync(p->update); - p->update = vc_dispmanx_update_start(10); - - return 0; -} - -static int set_geometry(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (vo->opts->fullscreen) { - vo->dwidth = p->w; - vo->dheight = p->h; - p->x = p->y = 0; - } else { - struct vo_win_geometry geo; - struct mp_rect screenrc = {0, 0, p->w, p->h}; - - vo_calc_window_geometry(vo, &screenrc, &geo); - vo_apply_window_geometry(vo, &geo); - - p->x = geo.win.x0; - p->y = geo.win.y0; - } - - resize(vo); - - if (create_overlays(vo) < 0) - return -1; - - return 0; -} - -static void wait_next_vsync(struct vo *vo) -{ - struct priv *p = vo->priv; - mp_mutex_lock(&p->display_mutex); - int64_t end = mp_time_ns() + MP_TIME_MS_TO_NS(50); - int64_t old = p->vsync_counter; - while (old == p->vsync_counter && !p->reload_display) { - if (mp_cond_timedwait_until(&p->display_cond, &p->display_mutex, end)) - break; - } - mp_mutex_unlock(&p->display_mutex); -} - -static void flip_page(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (!p->renderer_enabled) - return; - - struct mp_image *mpi = p->next_image; - p->next_image = NULL; - - // For OSD - if (!p->skip_osd && p->egl.gl) - eglSwapBuffers(p->egl.egl_display, p->egl.egl_surface); - p->skip_osd = false; - - if (mpi) { - MMAL_PORT_T *input = p->renderer->input[0]; - MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3]; - - // Assume this field is free for use by us. - ref->user_data = mpi; - - if (mmal_port_send_buffer(input, ref)) { - MP_ERR(vo, "could not queue picture!\n"); - talloc_free(mpi); - } - } - - if (p->display_synced) - wait_next_vsync(vo); -} - -static void free_mmal_buffer(void *arg) -{ - MMAL_BUFFER_HEADER_T *buffer = arg; - mmal_buffer_header_release(buffer); -} - -static void draw_frame(struct vo *vo, struct vo_frame *frame) -{ - struct priv *p = vo->priv; - - if (!p->renderer_enabled) - return; - - 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 = NULL; - - if (mpi) - p->osd_pts = mpi->pts; - - // Redraw only if the OSD has meaningfully changed, which we assume it - // hasn't when a frame is merely repeated for display sync. - p->skip_osd = !frame->redraw && frame->repeat; - - if (!p->skip_osd && p->egl.gl) - update_osd(vo); - - p->display_synced = frame->display_synced; - - if (mpi && mpi->imgfmt != IMGFMT_MMAL) { - MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue); - if (!buffer) { - talloc_free(mpi); - MP_ERR(vo, "Can't allocate buffer.\n"); - return; - } - mmal_buffer_header_reset(buffer); - - struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer, - free_mmal_buffer); - if (!new_ref) { - mmal_buffer_header_release(buffer); - talloc_free(mpi); - MP_ERR(vo, "Out of memory.\n"); - return; - } - - mp_image_setfmt(new_ref, IMGFMT_MMAL); - new_ref->planes[3] = (void *)buffer; - - struct mp_image dmpi = {0}; - buffer->length = layout_buffer(&dmpi, buffer, vo->params); - mp_image_copy(&dmpi, mpi); - - talloc_free(mpi); - mpi = new_ref; - } - - p->next_image = mpi; -} - -static int query_format(struct vo *vo, int format) -{ - return format == IMGFMT_MMAL || format == IMGFMT_420P; -} - -static MMAL_FOURCC_T map_csp(enum mp_csp csp) -{ - switch (csp) { - case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601; - case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709; - case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M; - default: return MMAL_COLOR_SPACE_UNKNOWN; - } -} - -static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - mmal_buffer_header_release(buffer); -} - -static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) -{ - struct mp_image *mpi = buffer->user_data; - talloc_free(mpi); -} - -static void disable_renderer(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (p->renderer_enabled) { - mmal_port_disable(p->renderer->control); - mmal_port_disable(p->renderer->input[0]); - - mmal_port_flush(p->renderer->control); - mmal_port_flush(p->renderer->input[0]); - - mmal_component_disable(p->renderer); - } - mmal_pool_destroy(p->swpool); - p->swpool = NULL; - p->renderer_enabled = false; -} - -static int reconfig(struct vo *vo, struct mp_image_params *params) -{ - struct priv *p = vo->priv; - MMAL_PORT_T *input = p->renderer->input[0]; - bool opaque = params->imgfmt == IMGFMT_MMAL; - - if (!p->display) - return -1; - - disable_renderer(vo); - - input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420; - input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W); - input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H); - input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h}; - input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h}; - input->format->es->video.color_space = map_csp(params->color.space); - - if (mmal_port_format_commit(input)) - return -1; - - input->buffer_num = MPMAX(input->buffer_num_min, - input->buffer_num_recommended) + 3; - input->buffer_size = MPMAX(input->buffer_size_min, - input->buffer_size_recommended); - - if (!opaque) { - size_t size = layout_buffer(&(struct mp_image){0}, NULL, params); - if (input->buffer_size != size) { - MP_FATAL(vo, "We disagree with MMAL about buffer sizes.\n"); - return -1; - } - - p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size); - if (!p->swpool) { - MP_FATAL(vo, "Could not allocate buffer pool.\n"); - return -1; - } - } - - if (set_geometry(vo) < 0) - return -1; - - p->renderer_enabled = true; - - if (mmal_port_enable(p->renderer->control, control_port_cb)) - return -1; - - if (mmal_port_enable(input, input_port_cb)) - return -1; - - if (mmal_component_enable(p->renderer)) { - MP_FATAL(vo, "Failed to enable video renderer.\n"); - return -1; - } - - resize(vo); - - return 0; -} - -static struct mp_image *take_screenshot(struct vo *vo) -{ - struct priv *p = vo->priv; - - if (!p->display) - return NULL; - - struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h); - if (!img) - return NULL; - - DISPMANX_RESOURCE_HANDLE_T resource = - vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, - img->w | ((img->w * 4) << 16), img->h, - &(int32_t){0}); - if (!resource) - goto fail; - - if (vc_dispmanx_snapshot(p->display, resource, 0)) - goto fail; - - VC_RECT_T rc = {.width = img->w, .height = img->h}; - if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0])) - goto fail; - - vc_dispmanx_resource_delete(resource); - return img; - -fail: - vc_dispmanx_resource_delete(resource); - talloc_free(img); - return NULL; -} - -static void set_fullscreen(struct vo *vo) { - struct priv *p = vo->priv; - - if (p->renderer_enabled) - set_geometry(vo); - vo->want_redraw = true; -} - -static int control(struct vo *vo, uint32_t request, void *data) -{ - struct priv *p = vo->priv; - - switch (request) { - case VOCTRL_VO_OPTS_CHANGED: { - void *opt; - while (m_config_cache_get_next_changed(p->opts_cache, &opt)) { - struct mp_vo_opts *opts = p->opts_cache->opts; - if (&opts->fullscreen == opt) - set_fullscreen(vo); - } - return VO_TRUE; - } - case VOCTRL_SET_PANSCAN: - if (p->renderer_enabled) - resize(vo); - vo->want_redraw = true; - return VO_TRUE; - case VOCTRL_REDRAW_FRAME: - update_osd(vo); - return VO_TRUE; - case VOCTRL_SCREENSHOT_WIN: - *(struct mp_image **)data = take_screenshot(vo); - return VO_TRUE; - case VOCTRL_CHECK_EVENTS: { - mp_mutex_lock(&p->display_mutex); - bool reload_required = p->reload_display; - p->reload_display = false; - mp_mutex_unlock(&p->display_mutex); - if (reload_required) - recreate_renderer(vo); - return VO_TRUE; - } - case VOCTRL_GET_DISPLAY_FPS: - *(double *)data = p->display_fps; - return VO_TRUE; - case VOCTRL_GET_DISPLAY_RES: - ((int *)data)[0] = p->w; - ((int *)data)[1] = p->h; - return VO_TRUE; - } - - return VO_NOTIMPL; -} - -static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1, - uint32_t param2) -{ - struct vo *vo = callback_data; - struct priv *p = vo->priv; - mp_mutex_lock(&p->display_mutex); - p->reload_display = true; - mp_cond_signal(&p->display_cond); - mp_mutex_unlock(&p->display_mutex); - vo_wakeup(vo); -} - -static void vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *arg) -{ - struct vo *vo = arg; - struct priv *p = vo->priv; - mp_mutex_lock(&p->display_mutex); - p->vsync_counter += 1; - mp_cond_signal(&p->display_cond); - mp_mutex_unlock(&p->display_mutex); -} - -static void destroy_dispmanx(struct vo *vo) -{ - struct priv *p = vo->priv; - - disable_renderer(vo); - destroy_overlays(vo); - - if (p->update) - vc_dispmanx_update_submit_sync(p->update); - p->update = 0; - - if (p->display) { - vc_dispmanx_vsync_callback(p->display, NULL, NULL); - vc_dispmanx_display_close(p->display); - } - p->display = 0; -} - -static int recreate_dispmanx(struct vo *vo) -{ - struct priv *p = vo->priv; - - p->display = vc_dispmanx_display_open(p->display_nr); - p->update = vc_dispmanx_update_start(0); - if (!p->display || !p->update) { - MP_FATAL(vo, "Could not get DISPMANX objects.\n"); - if (p->display) - vc_dispmanx_display_close(p->display); - p->display = 0; - p->update = 0; - return -1; - } - - update_display_size(vo); - - vc_dispmanx_vsync_callback(p->display, vsync_callback, vo); - - return 0; -} - -static void recreate_renderer(struct vo *vo) -{ - MP_WARN(vo, "Recreating renderer after display change.\n"); - - destroy_dispmanx(vo); - recreate_dispmanx(vo); - - if (vo->params) { - if (reconfig(vo, vo->params) < 0) - MP_FATAL(vo, "Recreation failed.\n"); - } -} - -static void uninit(struct vo *vo) -{ - struct priv *p = vo->priv; - - vc_tv_unregister_callback_full(tv_callback, vo); - - talloc_free(p->next_image); - - destroy_dispmanx(vo); - - if (p->renderer) - mmal_component_release(p->renderer); - - mmal_vc_deinit(); - - mp_cond_destroy(&p->display_cond); - mp_mutex_destroy(&p->display_mutex); -} - -static int preinit(struct vo *vo) -{ - struct priv *p = vo->priv; - - p->background_layer = p->layer; - p->video_layer = p->layer + 1; - p->osd_layer = p->layer + 2; - - p->egl.log = vo->log; - - bcm_host_init(); - - if (mmal_vc_init()) { - MP_FATAL(vo, "Could not initialize MMAL.\n"); - return -1; - } - - mp_mutex_init(&p->display_mutex); - mp_cond_init(&p->display_cond); - - p->opts_cache = m_config_cache_alloc(p, vo->global, &vo_sub_opts); - - if (recreate_dispmanx(vo) < 0) - goto fail; - - if (update_display_size(vo) < 0) - goto fail; - - if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer)) - { - MP_FATAL(vo, "Could not create MMAL renderer.\n"); - goto fail; - } - - vc_tv_register_callback(tv_callback, vo); - - return 0; - -fail: - uninit(vo); - return -1; -} - -#define OPT_BASE_STRUCT struct priv -static const struct m_option options[] = { - {"display", OPT_INT(display_nr)}, - {"layer", OPT_INT(layer), OPTDEF_INT(-10)}, - {"background", OPT_BOOL(background)}, - {"osd", OPT_BOOL(enable_osd), OPTDEF_INT(1)}, - {0}, -}; - -const struct vo_driver video_out_rpi = { - .description = "Raspberry Pi (MMAL)", - .name = "rpi", - .caps = VO_CAP_ROTATE90, - .preinit = preinit, - .query_format = query_format, - .reconfig = reconfig, - .control = control, - .draw_frame = draw_frame, - .flip_page = flip_page, - .uninit = uninit, - .priv_size = sizeof(struct priv), - .options = options, - .options_prefix = "rpi", -}; diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c index 5f4c027..71791e2 100644 --- a/video/out/vo_sdl.c +++ b/video/out/vo_sdl.c @@ -547,6 +547,10 @@ static void wait_events(struct vo *vo, int64_t until_time_ns) case SDL_WINDOWEVENT_LEAVE: mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_LEAVE); break; + case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_WINDOWEVENT_FOCUS_GAINED: + vo_event(vo, VO_EVENT_FOCUS); + break; } break; case SDL_QUIT: @@ -959,6 +963,9 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_UPDATE_WINDOW_TITLE: SDL_SetWindowTitle(vc->window, (char *)data); return true; + case VOCTRL_GET_FOCUSED: + *(bool *)data = SDL_GetWindowFlags(vc->window) & SDL_WINDOW_INPUT_FOCUS; + return VO_TRUE; } return VO_NOTIMPL; } diff --git a/video/out/vo_tct.c b/video/out/vo_tct.c index 8859095..0ca6ea6 100644 --- a/video/out/vo_tct.c +++ b/video/out/vo_tct.c @@ -39,17 +39,28 @@ #define ALGO_PLAIN 1 #define ALGO_HALF_BLOCKS 2 -#define TERM_ESC_CLEAR_COLORS "\033[0m" -#define TERM_ESC_COLOR256_BG "\033[48;5" -#define TERM_ESC_COLOR256_FG "\033[38;5" -#define TERM_ESC_COLOR24BIT_BG "\033[48;2" -#define TERM_ESC_COLOR24BIT_FG "\033[38;2" - #define DEFAULT_WIDTH 80 #define DEFAULT_HEIGHT 25 +static const bstr TERM_ESC_CLEAR_COLORS = bstr0_lit("\033[0m"); +static const bstr TERM_ESC_COLOR256_BG = bstr0_lit("\033[48;5"); +static const bstr TERM_ESC_COLOR256_FG = bstr0_lit("\033[38;5"); +static const bstr TERM_ESC_COLOR24BIT_BG = bstr0_lit("\033[48;2"); +static const bstr TERM_ESC_COLOR24BIT_FG = bstr0_lit("\033[38;2"); + +static const bstr UNICODE_LOWER_HALF_BLOCK = bstr0_lit("\xe2\x96\x84"); + +#define WRITE_STR(str) fwrite((str), strlen(str), 1, stdout) + +enum vo_tct_buffering { + VO_TCT_BUFFER_PIXEL, + VO_TCT_BUFFER_LINE, + VO_TCT_BUFFER_FRAME +}; + struct vo_tct_opts { int algo; + int buffering; int width; // 0 -> default int height; // 0 -> default bool term256; // 0 -> true color @@ -57,7 +68,7 @@ struct vo_tct_opts { struct lut_item { char str[4]; - int width; + uint8_t width; }; struct priv { @@ -69,6 +80,7 @@ struct priv { struct mp_rect src; struct mp_rect dst; struct mp_sws_context *sws; + bstr frame_buf; struct lut_item lut[256]; }; @@ -101,69 +113,65 @@ static int rgb_to_x256(uint8_t r, uint8_t g, uint8_t b) return color_err <= gray_err ? 16 + color_index() : 232 + gray_index; } -static void print_seq3(struct lut_item *lut, const char* prefix, +static void print_seq3(bstr *frame, struct lut_item *lut, bstr prefix, uint8_t r, uint8_t g, uint8_t b) { -// The fwrite implementation is about 25% faster than the printf code -// (even if we use *.s with the lut values), however, -// on windows we need to use printf in order to translate escape sequences and -// UTF8 output for the console. -#ifndef _WIN32 - fputs(prefix, stdout); - fwrite(lut[r].str, lut[r].width, 1, stdout); - fwrite(lut[g].str, lut[g].width, 1, stdout); - fwrite(lut[b].str, lut[b].width, 1, stdout); - fputc('m', stdout); -#else - printf("%s;%d;%d;%dm", prefix, (int)r, (int)g, (int)b); -#endif + bstr_xappend(NULL, frame, prefix); + bstr_xappend(NULL, frame, (bstr){ lut[r].str, lut[r].width }); + bstr_xappend(NULL, frame, (bstr){ lut[g].str, lut[g].width }); + bstr_xappend(NULL, frame, (bstr){ lut[b].str, lut[b].width }); + bstr_xappend(NULL, frame, (bstr)bstr0_lit("m")); } -static void print_seq1(struct lut_item *lut, const char* prefix, uint8_t c) +static void print_seq1(bstr *frame, struct lut_item *lut, bstr prefix, uint8_t c) { -#ifndef _WIN32 - fputs(prefix, stdout); - fwrite(lut[c].str, lut[c].width, 1, stdout); - fputc('m', stdout); -#else - printf("%s;%dm", prefix, (int)c); -#endif + bstr_xappend(NULL, frame, prefix); + bstr_xappend(NULL, frame, (bstr){ lut[c].str, lut[c].width }); + bstr_xappend(NULL, frame, (bstr)bstr0_lit("m")); } +static void print_buffer(bstr *frame) +{ + fwrite(frame->start, frame->len, 1, stdout); + frame->len = 0; +} -static void write_plain( +static void write_plain(bstr *frame, const int dwidth, const int dheight, const int swidth, const int sheight, const unsigned char *source, const int source_stride, - bool term256, struct lut_item *lut) + bool term256, struct lut_item *lut, enum vo_tct_buffering buffering) { assert(source); const int tx = (dwidth - swidth) / 2; const int ty = (dheight - sheight) / 2; for (int y = 0; y < sheight; y++) { const unsigned char *row = source + y * source_stride; - printf(TERM_ESC_GOTO_YX, ty + y, tx); + bstr_xappend_asprintf(NULL, frame, TERM_ESC_GOTO_YX, ty + y, tx); for (int x = 0; x < swidth; x++) { unsigned char b = *row++; unsigned char g = *row++; unsigned char r = *row++; if (term256) { - print_seq1(lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r, g, b)); + print_seq1(frame, lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r, g, b)); } else { - print_seq3(lut, TERM_ESC_COLOR24BIT_BG, r, g, b); + print_seq3(frame, lut, TERM_ESC_COLOR24BIT_BG, r, g, b); } - printf(" "); + bstr_xappend(NULL, frame, (bstr)bstr0_lit(" ")); + if (buffering <= VO_TCT_BUFFER_PIXEL) + print_buffer(frame); } - printf(TERM_ESC_CLEAR_COLORS); + bstr_xappend(NULL, frame, TERM_ESC_CLEAR_COLORS); + if (buffering <= VO_TCT_BUFFER_LINE) + print_buffer(frame); } - printf("\n"); } -static void write_half_blocks( +static void write_half_blocks(bstr *frame, const int dwidth, const int dheight, const int swidth, const int sheight, unsigned char *source, int source_stride, - bool term256, struct lut_item *lut) + bool term256, struct lut_item *lut, enum vo_tct_buffering buffering) { assert(source); const int tx = (dwidth - swidth) / 2; @@ -171,7 +179,7 @@ static void write_half_blocks( for (int y = 0; y < sheight * 2; y += 2) { const unsigned char *row_up = source + y * source_stride; const unsigned char *row_down = source + (y + 1) * source_stride; - printf(TERM_ESC_GOTO_YX, ty + y / 2, tx); + bstr_xappend_asprintf(NULL, frame, TERM_ESC_GOTO_YX, ty + y / 2, tx); for (int x = 0; x < swidth; x++) { unsigned char b_up = *row_up++; unsigned char g_up = *row_up++; @@ -180,17 +188,20 @@ static void write_half_blocks( unsigned char g_down = *row_down++; unsigned char r_down = *row_down++; if (term256) { - print_seq1(lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up)); - print_seq1(lut, TERM_ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down)); + print_seq1(frame, lut, TERM_ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up)); + print_seq1(frame, lut, TERM_ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down)); } else { - print_seq3(lut, TERM_ESC_COLOR24BIT_BG, r_up, g_up, b_up); - print_seq3(lut, TERM_ESC_COLOR24BIT_FG, r_down, g_down, b_down); + print_seq3(frame, lut, TERM_ESC_COLOR24BIT_BG, r_up, g_up, b_up); + print_seq3(frame, lut, TERM_ESC_COLOR24BIT_FG, r_down, g_down, b_down); } - printf("\xe2\x96\x84"); // UTF8 bytes of U+2584 (lower half block) + bstr_xappend(NULL, frame, UNICODE_LOWER_HALF_BLOCK); + if (buffering <= VO_TCT_BUFFER_PIXEL) + print_buffer(frame); } - printf(TERM_ESC_CLEAR_COLORS); + bstr_xappend(NULL, frame, TERM_ESC_CLEAR_COLORS); + if (buffering <= VO_TCT_BUFFER_LINE) + print_buffer(frame); } - printf("\n"); } static void get_win_size(struct vo *vo, int *out_width, int *out_height) { @@ -236,7 +247,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (mp_sws_reinit(p->sws) < 0) return -1; - printf(TERM_ESC_CLEAR_SCREEN); + WRITE_STR(TERM_ESC_CLEAR_SCREEN); vo->want_redraw = true; return 0; @@ -262,27 +273,36 @@ static void flip_page(struct vo *vo) if (vo->dwidth != width || vo->dheight != height) reconfig(vo, vo->params); + WRITE_STR(TERM_ESC_SYNC_UPDATE_BEGIN); + + p->frame_buf.len = 0; if (p->opts.algo == ALGO_PLAIN) { - write_plain( + write_plain(&p->frame_buf, vo->dwidth, vo->dheight, p->swidth, p->sheight, p->frame->planes[0], p->frame->stride[0], - p->opts.term256, p->lut); + p->opts.term256, p->lut, p->opts.buffering); } else { - write_half_blocks( + write_half_blocks(&p->frame_buf, vo->dwidth, vo->dheight, p->swidth, p->sheight, p->frame->planes[0], p->frame->stride[0], - p->opts.term256, p->lut); + p->opts.term256, p->lut, p->opts.buffering); } + + bstr_xappend(NULL, &p->frame_buf, (bstr)bstr0_lit("\n")); + if (p->opts.buffering <= VO_TCT_BUFFER_FRAME) + print_buffer(&p->frame_buf); + + WRITE_STR(TERM_ESC_SYNC_UPDATE_END); fflush(stdout); } static void uninit(struct vo *vo) { - printf(TERM_ESC_RESTORE_CURSOR); - printf(TERM_ESC_NORMAL_SCREEN); + WRITE_STR(TERM_ESC_RESTORE_CURSOR); + WRITE_STR(TERM_ESC_NORMAL_SCREEN); struct priv *p = vo->priv; - if (p->frame) - talloc_free(p->frame); + talloc_free(p->frame); + talloc_free(p->frame_buf.start); } static int preinit(struct vo *vo) @@ -296,14 +316,19 @@ static int preinit(struct vo *vo) p->sws->log = vo->log; mp_sws_enable_cmdline_opts(p->sws, vo->global); - for (int i = 0; i < 256; ++i) { - char buff[8]; - p->lut[i].width = snprintf(buff, sizeof(buff), ";%d", i); - memcpy(p->lut[i].str, buff, 4); // some strings may not end on a null byte, but that's ok. + for (int i = 0; i < MP_ARRAY_SIZE(p->lut); ++i) { + char* out = p->lut[i].str; + *out++ = ';'; + if (i >= 100) + *out++ = '0' + (i / 100); + if (i >= 10) + *out++ = '0' + ((i / 10) % 10); + *out++ = '0' + (i % 10); + p->lut[i].width = out - p->lut[i].str; } - printf(TERM_ESC_HIDE_CURSOR); - printf(TERM_ESC_ALT_SCREEN); + WRITE_STR(TERM_ESC_HIDE_CURSOR); + WRITE_STR(TERM_ESC_ALT_SCREEN); return 0; } @@ -333,6 +358,7 @@ const struct vo_driver video_out_tct = { .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .opts.algo = ALGO_HALF_BLOCKS, + .opts.buffering = VO_TCT_BUFFER_LINE, }, .options = (const m_option_t[]) { {"algo", OPT_CHOICE(opts.algo, @@ -341,6 +367,10 @@ const struct vo_driver video_out_tct = { {"width", OPT_INT(opts.width)}, {"height", OPT_INT(opts.height)}, {"256", OPT_BOOL(opts.term256)}, + {"buffering", OPT_CHOICE(opts.buffering, + {"pixel", VO_TCT_BUFFER_PIXEL}, + {"line", VO_TCT_BUFFER_LINE}, + {"frame", VO_TCT_BUFFER_FRAME})}, {0} }, .options_prefix = "vo-tct", diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c index 12888fe..2a24130 100644 --- a/video/out/vo_vaapi.c +++ b/video/out/vo_vaapi.c @@ -519,7 +519,7 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi) CHECK_VA_STATUS(p, "vaAssociateSubpicture()"); } - int flags = va_get_colorspace_flag(p->image_params.color.space) | + int flags = va_get_colorspace_flag(p->image_params.repr.sys) | p->scaling | VA_FRAME_PICTURE; status = vaPutSurface(p->display, surface, diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index d6b261f..b22dcba 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -80,7 +80,7 @@ struct vdpctx { struct mp_image *current_image; int64_t current_pts; - int current_duration; + int64_t current_duration; int output_surface_w, output_surface_h; int rotation; @@ -104,9 +104,9 @@ struct vdpctx { int surface_num; // indexes output_surfaces int query_surface_num; VdpTime recent_vsync_time; - float user_fps; + double user_fps; bool composite_detect; - int vsync_interval; + int64_t vsync_interval; uint64_t last_queue_time; uint64_t queue_time[MAX_OUTPUT_SURFACES]; uint64_t last_ideal_time; @@ -721,12 +721,12 @@ static int update_presentation_queue_status(struct vo *vo) break; if (vc->vsync_interval > 1) { uint64_t qtime = vc->queue_time[vc->query_surface_num]; - int diff = ((int64_t)vtime - (int64_t)qtime) / 1e6; - MP_TRACE(vo, "Queue time difference: %d ms\n", diff); + double diff = MP_TIME_NS_TO_MS((int64_t)vtime - (int64_t)qtime); + MP_TRACE(vo, "Queue time difference: %.4f ms\n", diff); if (vtime < qtime + vc->vsync_interval / 2) - MP_VERBOSE(vo, "Frame shown too early (%d ms)\n", diff); + MP_VERBOSE(vo, "Frame shown too early (%.4f ms)\n", diff); if (vtime > qtime + vc->vsync_interval) - MP_VERBOSE(vo, "Frame shown late (%d ms)\n", diff); + MP_VERBOSE(vo, "Frame shown late (%.4f ms)\n", diff); } vc->query_surface_num = WRAP_ADD(vc->query_surface_num, 1, vc->num_output_surfaces); @@ -754,8 +754,8 @@ static void flip_page(struct vo *vo) struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; - int64_t pts_us = vc->current_pts; - int duration = vc->current_duration; + int64_t pts_ns = vc->current_pts; + int64_t duration = vc->current_duration; vc->dropped_frame = true; // changed at end if false @@ -770,11 +770,6 @@ static void flip_page(struct vo *vo) } vc->vsync_interval = MPMAX(vc->vsync_interval, 1); - if (duration > INT_MAX / 1000) - duration = -1; - else - duration *= 1000; - if (vc->vsync_interval == 1) duration = -1; // Make sure drop logic is disabled @@ -782,8 +777,8 @@ static void flip_page(struct vo *vo) vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time); CHECK_VDP_WARNING(vo, "Error when calling vdp_presentation_queue_get_time"); - int64_t rel_pts_ns = (pts_us * 1000) - mp_time_ns(); - if (!pts_us || rel_pts_ns < 0) + int64_t rel_pts_ns = pts_ns - mp_time_ns(); + if (!pts_ns || rel_pts_ns < 0) rel_pts_ns = 0; uint64_t now = vdp_time; @@ -1076,9 +1071,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_SET_PANSCAN: checked_resize(vo); return VO_TRUE; - case VOCTRL_SET_EQUALIZER: - vo->want_redraw = true; - return true; case VOCTRL_RESET: forget_frames(vo, true); return true; @@ -1124,7 +1116,7 @@ const struct vo_driver video_out_vdpau = { {"denoise", OPT_FLOAT(denoise), M_RANGE(0, 1)}, {"sharpen", OPT_FLOAT(sharpen), M_RANGE(-1, 1)}, {"hqscaling", OPT_INT(hqscaling), M_RANGE(0, 9)}, - {"fps", OPT_FLOAT(user_fps)}, + {"fps", OPT_DOUBLE(user_fps)}, {"composite-detect", OPT_BOOL(composite_detect), OPTDEF_INT(1)}, {"queuetime-windowed", OPT_INT(flip_offset_window), OPTDEF_INT(50)}, {"queuetime-fs", OPT_INT(flip_offset_fs), OPTDEF_INT(50)}, diff --git a/video/out/vo_wlshm.c b/video/out/vo_wlshm.c index 1e5e009..0b63426 100644 --- a/video/out/vo_wlshm.c +++ b/video/out/vo_wlshm.c @@ -21,8 +21,6 @@ #include #include -#include - #include "osdep/endian.h" #include "present_sync.h" #include "sub/osd.h" @@ -32,6 +30,8 @@ #include "vo.h" #include "wayland_common.h" +#define IMGFMT_WL_RGB MP_SELECT_LE_BE(IMGFMT_BGR0, IMGFMT_0RGB) + struct buffer { struct vo *vo; size_t size; @@ -164,7 +164,8 @@ err: static int query_format(struct vo *vo, int format) { - return sws_isSupportedInput(imgfmt2pixfmt(format)); + struct priv *p = vo->priv; + return mp_sws_supports_formats(p->sws, IMGFMT_WL_RGB, format) ? 1 : 0; } static int reconfig(struct vo *vo, struct mp_image_params *params) @@ -195,21 +196,26 @@ static int resize(struct vo *vo) vo->dwidth = width; vo->dheight = height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); + p->sws->dst = (struct mp_image_params) { - .imgfmt = MP_SELECT_LE_BE(IMGFMT_BGR0, IMGFMT_0RGB), + .imgfmt = IMGFMT_WL_RGB, .w = width, .h = height, .p_w = 1, .p_h = 1, }; mp_image_params_guess_csp(&p->sws->dst); + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &p->sws->dst; + mp_mutex_unlock(&vo->params_mutex); + while (p->free_buffers) { buf = p->free_buffers; p->free_buffers = buf->next; talloc_free(buf); } - vo_wayland_handle_fractional_scale(wl); + vo_wayland_handle_scale(wl); return mp_sws_reinit(p->sws); } diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c index fa93157..b637b59 100644 --- a/video/out/vo_x11.c +++ b/video/out/vo_x11.c @@ -266,6 +266,10 @@ static bool resize(struct vo *vo) if (mp_sws_reinit(p->sws) < 0) return false; + + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &p->sws->dst; + mp_mutex_unlock(&vo->params_mutex); } vo->want_redraw = true; diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c index 6c776c5..d2d5b73 100644 --- a/video/out/vo_xv.c +++ b/video/out/vo_xv.c @@ -92,6 +92,7 @@ struct xvctx { int Shmem_Flag; XShmSegmentInfo Shminfo[MAX_BUFFERS]; int Shm_Warned_Slow; + struct mp_image_params dst_params; }; #define MP_FOURCC(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((unsigned)(d)<<24)) @@ -401,7 +402,7 @@ static void read_xv_csp(struct vo *vo) ctx->cached_csp = 0; int bt709_enabled; if (xv_get_eq(vo, ctx->xv_port, "bt_709", &bt709_enabled)) - ctx->cached_csp = bt709_enabled == 100 ? MP_CSP_BT_709 : MP_CSP_BT_601; + ctx->cached_csp = bt709_enabled == 100 ? PL_COLOR_SYSTEM_BT_709 : PL_COLOR_SYSTEM_BT_601; } @@ -519,10 +520,17 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) ctx->current_buf = 0; ctx->current_ip_buf = 0; - int is_709 = params->color.space == MP_CSP_BT_709; + int is_709 = params->repr.sys == PL_COLOR_SYSTEM_BT_709; xv_set_eq(vo, ctx->xv_port, "bt_709", is_709 * 200 - 100); read_xv_csp(vo); + ctx->dst_params = *params; + if (ctx->cached_csp) + ctx->dst_params.repr.sys = ctx->cached_csp; + mp_mutex_lock(&vo->params_mutex); + vo->target_params = &ctx->dst_params; + mp_mutex_unlock(&vo->params_mutex); + resize(vo); return 0; @@ -652,7 +660,7 @@ static struct mp_image get_xv_buffer(struct vo *vo, int buf_index) if (vo->params) { struct mp_image_params params = *vo->params; if (ctx->cached_csp) - params.color.space = ctx->cached_csp; + params.repr.sys = ctx->cached_csp; mp_image_set_attributes(&img, ¶ms); } diff --git a/video/out/vulkan/common.h b/video/out/vulkan/common.h index d006942..e75cb22 100644 --- a/video/out/vulkan/common.h +++ b/video/out/vulkan/common.h @@ -23,7 +23,6 @@ #define VK_USE_PLATFORM_WIN32_KHR #endif #if HAVE_COCOA -#define VK_USE_PLATFORM_MACOS_MVK #define VK_USE_PLATFORM_METAL_EXT #endif diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c index 5087403..82c878a 100644 --- a/video/out/vulkan/context.c +++ b/video/out/vulkan/context.c @@ -25,6 +25,7 @@ #include "options/m_config.h" #include "video/out/placebo/ra_pl.h" +#include "video/out/placebo/utils.h" #include "context.h" #include "utils.h" @@ -37,39 +38,32 @@ struct vulkan_opts { bool async_compute; }; -static int vk_validate_dev(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) +static inline OPT_STRING_VALIDATE_FUNC(vk_validate_dev) { - struct bstr param = bstr0(*value); int ret = M_OPT_INVALID; - VkResult res; + void *ta_ctx = talloc_new(NULL); + pl_log pllog = mppl_log_create(ta_ctx, log); + if (!pllog) + goto done; // Create a dummy instance to validate/list the devices - VkInstanceCreateInfo info = { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pApplicationInfo = &(VkApplicationInfo) { - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .apiVersion = VK_API_VERSION_1_1, - } - }; - - VkInstance inst; - VkPhysicalDevice *devices = NULL; - uint32_t num = 0; - - res = vkCreateInstance(&info, NULL, &inst); - if (res != VK_SUCCESS) + mppl_log_set_probing(pllog, true); + pl_vk_inst inst = pl_vk_inst_create(pllog, pl_vk_inst_params()); + mppl_log_set_probing(pllog, false); + if (!inst) goto done; - res = vkEnumeratePhysicalDevices(inst, &num, NULL); + uint32_t num = 0; + VkResult res = vkEnumeratePhysicalDevices(inst->instance, &num, NULL); if (res != VK_SUCCESS) goto done; - devices = talloc_array(NULL, VkPhysicalDevice, num); - res = vkEnumeratePhysicalDevices(inst, &num, devices); + VkPhysicalDevice *devices = talloc_array(ta_ctx, VkPhysicalDevice, num); + res = vkEnumeratePhysicalDevices(inst->instance, &num, devices); if (res != VK_SUCCESS) goto done; + struct bstr param = bstr0(*value); bool help = bstr_equals0(param, "help"); if (help) { mp_info(log, "Available vulkan devices:\n"); @@ -111,7 +105,9 @@ static int vk_validate_dev(struct mp_log *log, const struct m_option *opt, BSTR_P(param)); done: - talloc_free(devices); + pl_vk_inst_destroy(&inst); + pl_log_destroy(&pllog); + talloc_free(ta_ctx); return ret; } @@ -207,8 +203,7 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk, VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME, VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME, VK_KHR_VIDEO_QUEUE_EXTENSION_NAME, - // This is a literal string as it's not in the official headers yet. - "VK_MESA_video_decode_av1", + "VK_KHR_video_decode_av1", /* VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME */ }; VkPhysicalDeviceDescriptorBufferFeaturesEXT descriptor_buffer_feature = { @@ -310,11 +305,6 @@ char *ra_vk_ctx_get_device_name(struct ra_ctx *ctx) return device_name; } -static int color_depth(struct ra_swapchain *sw) -{ - return 0; // TODO: implement this somehow? -} - static bool start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo) { struct priv *p = sw->priv; @@ -364,7 +354,6 @@ static void get_vsync(struct ra_swapchain *sw, } static const struct ra_swapchain_fns vulkan_swapchain = { - .color_depth = color_depth, .start_frame = start_frame, .submit_frame = submit_frame, .swap_buffers = swap_buffers, diff --git a/video/out/vulkan/context_display.c b/video/out/vulkan/context_display.c index 84cef1e..72f73ad 100644 --- a/video/out/vulkan/context_display.c +++ b/video/out/vulkan/context_display.c @@ -15,9 +15,11 @@ * License along with mpv. If not, see . */ +#include "common/common.h" #include "context.h" #include "options/m_config.h" #include "utils.h" +#include "video/out/placebo/utils.h" #if HAVE_DRM #include @@ -214,35 +216,36 @@ done: } static int print_display_info(struct mp_log *log, const struct m_option *opt, - struct bstr name) { - VkResult res; - VkPhysicalDevice *devices = NULL; + struct bstr name) +{ + void *ta_ctx = talloc_new(NULL); + pl_log pllog = mppl_log_create(ta_ctx, log); + if (!pllog) + goto done; // Create a dummy instance to list the resources - VkInstanceCreateInfo info = { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .enabledExtensionCount = 1, - .ppEnabledExtensionNames = (const char*[]) { - VK_KHR_DISPLAY_EXTENSION_NAME + mppl_log_set_probing(pllog, true); + pl_vk_inst inst = pl_vk_inst_create(pllog, pl_vk_inst_params( + .extensions = (const char *[]){ + VK_KHR_DISPLAY_EXTENSION_NAME, }, - }; - - VkInstance inst = NULL; - res = vkCreateInstance(&info, NULL, &inst); - if (res != VK_SUCCESS) { + .num_extensions = 1, + )); + mppl_log_set_probing(pllog, false); + if (!inst) { mp_warn(log, "Unable to create Vulkan instance.\n"); goto done; } uint32_t num_devices = 0; - vkEnumeratePhysicalDevices(inst, &num_devices, NULL); - if (!num_devices) { + VkResult res = vkEnumeratePhysicalDevices(inst->instance, &num_devices, NULL); + if (res != VK_SUCCESS || !num_devices) { mp_info(log, "No Vulkan devices detected.\n"); goto done; } - devices = talloc_array(NULL, VkPhysicalDevice, num_devices); - vkEnumeratePhysicalDevices(inst, &num_devices, devices); + VkPhysicalDevice *devices = talloc_array(ta_ctx, VkPhysicalDevice, num_devices); + res = vkEnumeratePhysicalDevices(inst->instance, &num_devices, devices); if (res != VK_SUCCESS) { mp_warn(log, "Failed enumerating physical devices.\n"); goto done; @@ -254,8 +257,9 @@ static int print_display_info(struct mp_log *log, const struct m_option *opt, } done: - talloc_free(devices); - vkDestroyInstance(inst, NULL); + pl_vk_inst_destroy(&inst); + pl_log_destroy(&pllog); + talloc_free(ta_ctx); return M_OPT_EXIT; } @@ -296,7 +300,7 @@ static void open_render_fd(struct ra_ctx *ctx, const char *render_path) p->drm_params.render_fd = open(render_path, O_RDWR | O_CLOEXEC); if (p->drm_params.render_fd == -1) { MP_WARN(ctx, "Failed to open render node: %s\n", - strerror(errno)); + mp_strerror(errno)); } } diff --git a/video/out/vulkan/context_mac.m b/video/out/vulkan/context_mac.m index 8ac6e16..bedd0d4 100644 --- a/video/out/vulkan/context_mac.m +++ b/video/out/vulkan/context_mac.m @@ -15,8 +15,10 @@ * License along with mpv. If not, see . */ +#import + #include "video/out/gpu/context.h" -#include "osdep/macOS_swift.h" +#include "osdep/mac/swift.h" #include "common.h" #include "context.h" @@ -42,6 +44,12 @@ static void mac_vk_swap_buffers(struct ra_ctx *ctx) [p->vo_mac swapBuffer]; } +static void mac_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info) +{ + struct priv *p = ctx->priv; + [p->vo_mac fillVsyncWithInfo:info]; +} + static bool mac_vk_init(struct ra_ctx *ctx) { struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); @@ -56,7 +64,7 @@ static bool mac_vk_init(struct ra_ctx *ctx) goto error; VkMetalSurfaceCreateInfoEXT mac_info = { - .sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, + .sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, .pNext = NULL, .flags = 0, .pLayer = p->vo_mac.layer, @@ -64,6 +72,7 @@ static bool mac_vk_init(struct ra_ctx *ctx) struct ra_vk_ctx_params params = { .swap_buffers = mac_vk_swap_buffers, + .get_vsync = mac_vk_get_vsync, }; VkInstance inst = vk->vkinst->instance; @@ -85,7 +94,14 @@ error: static bool resize(struct ra_ctx *ctx) { - return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight); + struct priv *p = ctx->priv; + + if (!p->vo_mac.window) { + return false; + } + CGSize size = p->vo_mac.window.framePixel.size; + + return ra_vk_ctx_resize(ctx, (int)size.width, (int)size.height); } static bool mac_vk_reconfig(struct ra_ctx *ctx) diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c index 761ff5b..cdf1ba6 100644 --- a/video/out/vulkan/context_wayland.c +++ b/video/out/vulkan/context_wayland.c @@ -118,7 +118,7 @@ static bool resize(struct ra_ctx *ctx) const int32_t height = mp_rect_h(wl->geometry); vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha); - vo_wayland_handle_fractional_scale(wl); + vo_wayland_handle_scale(wl); return ra_vk_ctx_resize(ctx, width, height); } diff --git a/video/out/vulkan/context_win.c b/video/out/vulkan/context_win.c index a89c644..328753f 100644 --- a/video/out/vulkan/context_win.c +++ b/video/out/vulkan/context_win.c @@ -50,6 +50,9 @@ static bool win_init(struct ra_ctx *ctx) if (!vo_w32_init(ctx->vo)) goto error; + if (ctx->opts.want_alpha) + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); + VkWin32SurfaceCreateInfoKHR wininfo = { .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, .hinstance = HINST_THISCOMPONENT, @@ -96,11 +99,17 @@ static int win_control(struct ra_ctx *ctx, int *events, int request, void *arg) return ret; } +static void win_update_render_opts(struct ra_ctx *ctx) +{ + vo_w32_set_transparency(ctx->vo, ctx->opts.want_alpha); +} + 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, + .type = "vulkan", + .name = "winvk", + .reconfig = win_reconfig, + .control = win_control, + .update_render_opts = win_update_render_opts, + .init = win_init, + .uninit = win_uninit, }; diff --git a/video/out/w32_common.c b/video/out/w32_common.c index e6a4670..36f48b9 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -40,6 +40,7 @@ #include "w32_common.h" #include "win32/displayconfig.h" #include "win32/droptarget.h" +#include "win32/menu.h" #include "osdep/io.h" #include "osdep/threads.h" #include "osdep/w32_keyboard.h" @@ -58,10 +59,17 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif +#ifndef DWMWA_VISIBLE_FRAME_BORDER_THICKNESS +#define DWMWA_VISIBLE_FRAME_BORDER_THICKNESS 37 +#endif -//Older MinGW compatibility +#ifndef DWMWA_WINDOW_CORNER_PREFERENCE #define DWMWA_WINDOW_CORNER_PREFERENCE 33 +#endif + +#ifndef DWMWA_SYSTEMBACKDROP_TYPE #define DWMWA_SYSTEMBACKDROP_TYPE 38 +#endif #ifndef DPI_ENUMS_DECLARED typedef enum MONITOR_DPI_TYPE { @@ -75,10 +83,12 @@ typedef enum MONITOR_DPI_TYPE { #define rect_w(r) ((r).right - (r).left) #define rect_h(r) ((r).bottom - (r).top) +#define WM_SHOWMENU (WM_USER + 1) + struct w32_api { HRESULT (WINAPI *pGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - BOOL (WINAPI *pImmDisableIME)(DWORD); BOOL (WINAPI *pAdjustWindowRectExForDpi)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); + int (WINAPI *pGetSystemMetricsForDpi)(int nIndex, UINT dpi); BOOLEAN (WINAPI *pShouldAppsUseDarkMode)(void); DWORD (WINAPI *pSetPreferredAppMode)(DWORD mode); }; @@ -102,6 +112,8 @@ struct vo_w32_state { HHOOK parent_win_hook; HWINEVENTHOOK parent_evt_hook; + struct menu_ctx *menu_ctx; + HMONITOR monitor; // Handle of the current screen char *color_profile; // Path of the current screen's color profile @@ -132,7 +144,7 @@ struct vo_w32_state { atomic_uint event_flags; BOOL tracking; - TRACKMOUSEEVENT trackEvent; + TRACKMOUSEEVENT track_event; int mouse_x; int mouse_y; @@ -156,8 +168,8 @@ struct vo_w32_state { ITaskbarList2 *taskbar_list; ITaskbarList3 *taskbar_list3; - UINT tbtnCreatedMsg; - bool tbtnCreated; + UINT tbtn_created_msg; + bool tbtn_created; struct voctrl_playback_state current_pstate; @@ -181,8 +193,21 @@ struct vo_w32_state { HANDLE avrt_handle; bool cleared; + bool dragging; + bool start_dragging; + BOOL win_arranging; + + bool conversion_mode_init; + bool unmaximize; }; +static inline int get_system_metrics(struct vo_w32_state *w32, int metric) +{ + return w32->api.pGetSystemMetricsForDpi + ? w32->api.pGetSystemMetricsForDpi(metric, w32->dpi) + : GetSystemMetrics(metric); +} + static void adjust_window_rect(struct vo_w32_state *w32, HWND hwnd, RECT *rc) { if (!w32->opts->border) @@ -197,13 +222,67 @@ static void adjust_window_rect(struct vo_w32_state *w32, HWND hwnd, RECT *rc) } } +static bool check_windows10_build(DWORD build) +{ + OSVERSIONINFOEXW osvi = { + .dwOSVersionInfoSize = sizeof(osvi), + .dwMajorVersion = HIBYTE(_WIN32_WINNT_WIN10), + .dwMinorVersion = LOBYTE(_WIN32_WINNT_WIN10), + .dwBuildNumber = build, + }; + + DWORD type = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER; + + ULONGLONG mask = 0; + mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_BUILDNUMBER, VER_GREATER_EQUAL); + + return VerifyVersionInfoW(&osvi, type, mask); +} + +// Get adjusted title bar height, only relevant for --title-bar=no +static int get_title_bar_height(struct vo_w32_state *w32) +{ + assert(!w32->opts->title_bar && w32->opts->border); + UINT visible_border = 0; + // Only available on Windows 11, check in case it's backported and breaks + // WM_NCCALCSIZE exception for Windows 10. + if (check_windows10_build(22000)) { + DwmGetWindowAttribute(w32->window, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, + &visible_border, sizeof(visible_border)); + } + int top_bar = IsMaximized(w32->window) + ? get_system_metrics(w32, SM_CYFRAME) + + get_system_metrics(w32, SM_CXPADDEDBORDER) + : visible_border; + return top_bar; +} + static void add_window_borders(struct vo_w32_state *w32, HWND hwnd, RECT *rc) { RECT win = *rc; adjust_window_rect(w32, hwnd, rc); // Adjust for title bar height that will be hidden in WM_NCCALCSIZE - if (w32->opts->border && !w32->opts->title_bar && !w32->current_fs) - rc->top -= rc->top - win.top; + // Keep the frame border. On Windows 10 the top border is not retained. + // It appears that DWM draws the title bar with its full height, extending + // outside the window area. Essentially, there is a bug in DWM, preventing + // the adjustment of the title bar height. This issue occurs when both the + // top and left client areas are non-zero in WM_NCCALCSIZE. If the left NC + // area is set to 0, the title bar is drawn correctly with the adjusted + // height. To mitigate this problem, set the top NC area to zero. The issue + // doesn't happen on Windows 11 or when DWM NC drawing is disabled with + // DWMWA_NCRENDERING_POLICY. We aim to avoid the manual drawing the border + // and want the DWM look and feel, so skip the top border on Windows 10. + // Also DWMWA_VISIBLE_FRAME_BORDER_THICKNESS is available only on Windows 11, + // so it would be hard to guess this size correctly on Windows 10 anyway. + if (w32->opts->border && !w32->opts->title_bar && !w32->current_fs && + (GetWindowLongPtrW(w32->window, GWL_STYLE) & WS_CAPTION)) + { + if (!check_windows10_build(22000) && !IsMaximized(w32->window)) + *rc = win; + rc->top = win.top - get_title_bar_height(w32); + } } // basically a reverse AdjustWindowRect (win32 doesn't appear to have this) @@ -226,13 +305,13 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y) if (!GetWindowRect(w32->window, &rc)) return HTNOWHERE; - POINT frame = {GetSystemMetrics(SM_CXSIZEFRAME), - GetSystemMetrics(SM_CYSIZEFRAME)}; + POINT frame = {get_system_metrics(w32, SM_CXSIZEFRAME), + get_system_metrics(w32, SM_CYSIZEFRAME)}; if (w32->opts->border) { - frame.x += GetSystemMetrics(SM_CXPADDEDBORDER); - frame.y += GetSystemMetrics(SM_CXPADDEDBORDER); + frame.x += get_system_metrics(w32, SM_CXPADDEDBORDER); + frame.y += get_system_metrics(w32, SM_CXPADDEDBORDER); if (!w32->opts->title_bar) - rc.top -= GetSystemMetrics(SM_CXPADDEDBORDER); + rc.top -= get_system_metrics(w32, SM_CXPADDEDBORDER); } InflateRect(&rc, -frame.x, -frame.y); @@ -344,8 +423,8 @@ static void clear_keyboard_buffer(void) // Use the method suggested by Michael Kaplan to clear any pending dead // keys from the current keyboard layout. See: - // https://web.archive.org/web/20101004154432/http://blogs.msdn.com/b/michkap/archive/2006/04/06/569632.aspx - // https://web.archive.org/web/20100820152419/http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx + // + // do { ret = ToUnicode(vkey, scancode, keys, buf, MP_ARRAY_SIZE(buf), 0); } while (ret < 0); @@ -356,7 +435,7 @@ static int to_unicode(UINT vkey, UINT scancode, const BYTE keys[256]) // This wraps ToUnicode to be stateless and to return only one character // Make the buffer 10 code units long to be safe, same as here: - // https://web.archive.org/web/20101013215215/http://blogs.msdn.com/b/michkap/archive/2006/03/24/559169.aspx + // wchar_t buf[10] = { 0 }; // Dead keys aren't useful for key shortcuts, so clear the keyboard state @@ -428,10 +507,6 @@ static bool handle_appcommand(struct vo_w32_state *w32, UINT cmd) static void handle_key_down(struct vo_w32_state *w32, UINT vkey, UINT scancode) { - // Ignore key repeat - if (scancode & KF_REPEAT) - return; - int mpkey = mp_w32_vkey_to_mpkey(vkey, scancode & KF_EXTENDED); if (!mpkey) { mpkey = decode_key(w32, vkey, scancode & (0xff | KF_EXTENDED)); @@ -439,7 +514,8 @@ static void handle_key_down(struct vo_w32_state *w32, UINT vkey, UINT scancode) return; } - mp_input_put_key(w32->input_ctx, mpkey | mod_state(w32) | MP_KEY_STATE_DOWN); + int state = w32->opts->native_keyrepeat ? 0 : MP_KEY_STATE_DOWN; + mp_input_put_key(w32->input_ctx, mpkey | mod_state(w32) | state); } static void handle_key_up(struct vo_w32_state *w32, UINT vkey, UINT scancode) @@ -456,9 +532,9 @@ static void handle_key_up(struct vo_w32_state *w32, UINT vkey, UINT scancode) } } -static bool handle_char(struct vo_w32_state *w32, wchar_t wc) +static bool handle_char(struct vo_w32_state *w32, WPARAM wc, bool decode) { - int c = decode_utf16(w32, wc); + int c = decode ? decode_utf16(w32, wc) : wc; if (c == 0) return true; @@ -469,23 +545,33 @@ static bool handle_char(struct vo_w32_state *w32, wchar_t wc) return true; } +static void begin_dragging(struct vo_w32_state *w32) +{ + if (w32->current_fs || + mp_input_test_dragging(w32->input_ctx, w32->mouse_x, w32->mouse_y)) + return; + // Window dragging hack + ReleaseCapture(); + // The dragging model loop is entered at SendMessage() here. + // Unfortunately, the w32->current_fs value is stale because the + // input is handled in a different thread, and we cannot wait for + // an up-to-date value before entering the model loop if dragging + // needs to be kept resonsive. + // Workaround this by intercepting the loop in the WM_MOVING message, + // where the up-to-date value is available. + SystemParametersInfoW(SPI_GETWINARRANGING, 0, &w32->win_arranging, 0); + w32->dragging = true; + SendMessage(w32->window, WM_NCLBUTTONDOWN, HTCAPTION, 0); + w32->dragging = false; + SystemParametersInfoW(SPI_SETWINARRANGING, w32->win_arranging, 0, 0); + + mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL); +} + static bool handle_mouse_down(struct vo_w32_state *w32, int btn, int x, int y) { btn |= mod_state(w32); mp_input_put_key(w32->input_ctx, btn | MP_KEY_STATE_DOWN); - - if (btn == MP_MBTN_LEFT && !w32->current_fs && - !mp_input_test_dragging(w32->input_ctx, x, y)) - { - // Window dragging hack - ReleaseCapture(); - SendMessage(w32->window, WM_NCLBUTTONDOWN, HTCAPTION, 0); - mp_input_put_key(w32->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); - - // Indicate the message was handled, so DefWindowProc won't be called - return true; - } - SetCapture(w32->window); return false; } @@ -566,18 +652,20 @@ static double get_refresh_rate_from_gdi(const wchar_t *device) static char *get_color_profile(void *ctx, const wchar_t *device) { char *name = NULL; + wchar_t *wname = NULL; HDC ic = CreateICW(device, NULL, NULL, NULL); if (!ic) goto done; - wchar_t wname[MAX_PATH + 1]; - if (!GetICMProfileW(ic, &(DWORD){ MAX_PATH }, wname)) + wname = talloc_array(NULL, wchar_t, MP_PATH_MAX); + if (!GetICMProfileW(ic, &(DWORD){ MP_PATH_MAX - 1 }, wname)) goto done; name = mp_to_utf8(ctx, wname); done: if (ic) DeleteDC(ic); + talloc_free(wname); return name; } @@ -603,7 +691,7 @@ static void update_dpi(struct vo_w32_state *w32) } w32->dpi = dpi; - w32->dpi_scale = w32->opts->hidpi_window_scale ? w32->dpi / 96.0 : 1.0; + w32->dpi_scale = w32->dpi / 96.0; signal_events(w32, VO_EVENT_DPI); } @@ -660,7 +748,7 @@ static void update_playback_state(struct vo_w32_state *w32) { struct voctrl_playback_state *pstate = &w32->current_pstate; - if (!w32->taskbar_list3 || !w32->tbtnCreated) + if (!w32->taskbar_list3 || !w32->tbtn_created) return; if (!pstate->playing || !pstate->taskbar_progress) { @@ -743,10 +831,10 @@ static RECT get_screen_area(struct vo_w32_state *w32) { // Handle --fs-screen=all if (w32->current_fs && w32->opts->fsscreen_id == -2) { - const int x = GetSystemMetrics(SM_XVIRTUALSCREEN); - const int y = GetSystemMetrics(SM_YVIRTUALSCREEN); - return (RECT) { x, y, x + GetSystemMetrics(SM_CXVIRTUALSCREEN), - y + GetSystemMetrics(SM_CYVIRTUALSCREEN) }; + const int x = get_system_metrics(w32, SM_XVIRTUALSCREEN); + const int y = get_system_metrics(w32, SM_YVIRTUALSCREEN); + return (RECT) { x, y, x + get_system_metrics(w32, SM_CXVIRTUALSCREEN), + y + get_system_metrics(w32, SM_CYVIRTUALSCREEN) }; } return get_monitor_info(w32).rcMonitor; } @@ -842,6 +930,13 @@ static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc) return true; } +static bool is_high_contrast(void) +{ + HIGHCONTRAST hc = {sizeof(hc)}; + SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0); + return hc.dwFlags & HCF_HIGHCONTRASTON; +} + static DWORD update_style(struct vo_w32_state *w32, DWORD style) { const DWORD NO_FRAME = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_THICKFRAME; @@ -853,17 +948,12 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style) style |= FULLSCREEN; } else { style |= w32->opts->border ? FRAME : NO_FRAME; + if (!w32->opts->title_bar && is_high_contrast()) + style &= ~WS_CAPTION; } return style; } -static LONG get_title_bar_height(struct vo_w32_state *w32) -{ - RECT rc = {0}; - adjust_window_rect(w32, w32->window, &rc); - return -rc.top; -} - static void update_window_style(struct vo_w32_state *w32) { if (w32->parent) @@ -1006,14 +1096,13 @@ static void update_minimized_state(struct vo_w32_state *w32) } } -static void update_maximized_state(struct vo_w32_state *w32) +static void update_maximized_state(struct vo_w32_state *w32, bool leaving_fullscreen) { if (w32->parent) return; - // Don't change the maximized state in fullscreen for now. In future, this - // should be made to apply the maximized state on leaving fullscreen. - if (w32->current_fs) + // Apply the maximized state on leaving fullscreen. + if (w32->current_fs && !leaving_fullscreen) return; WINDOWPLACEMENT wp = { .length = sizeof wp }; @@ -1065,11 +1154,24 @@ static void update_window_state(struct vo_w32_state *w32) wr.left, wr.top, rect_w(wr), rect_h(wr), SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + // Unmaximize the window if a size change is requested because SetWindowPos + // doesn't change the window maximized state. + // ShowWindow(SW_SHOWNOACTIVATE) can't be used here because it tries to + // "restore" the window to its size before it's maximized. + if (w32->unmaximize) { + WINDOWPLACEMENT wp = { .length = sizeof wp }; + GetWindowPlacement(w32->window, &wp); + wp.showCmd = SW_SHOWNOACTIVATE; + wp.rcNormalPosition = wr; + SetWindowPlacement(w32->window, &wp); + w32->unmaximize = false; + } + // Show the window if it's not yet visible if (!is_visible(w32->window)) { if (w32->opts->window_minimized) { ShowWindow(w32->window, SW_SHOWMINNOACTIVE); - update_maximized_state(w32); // Set the WPF_RESTORETOMAXIMIZED flag + update_maximized_state(w32, false); // Set the WPF_RESTORETOMAXIMIZED flag } else if (w32->opts->window_maximized) { ShowWindow(w32->window, SW_SHOWMAXIMIZED); } else { @@ -1166,13 +1268,9 @@ static void update_dark_mode(const struct vo_w32_state *w32) if (w32->api.pSetPreferredAppMode) w32->api.pSetPreferredAppMode(1); // allow dark mode - HIGHCONTRAST hc = {sizeof(hc)}; - SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0); - bool high_contrast = hc.dwFlags & HCF_HIGHCONTRASTON; - // if pShouldAppsUseDarkMode is not available, just assume it to be true - const BOOL use_dark_mode = !high_contrast && (!w32->api.pShouldAppsUseDarkMode || - w32->api.pShouldAppsUseDarkMode()); + const BOOL use_dark_mode = !is_high_contrast() && (!w32->api.pShouldAppsUseDarkMode || + w32->api.pShouldAppsUseDarkMode()); SetWindowTheme(w32->window, use_dark_mode ? L"DarkMode_Explorer" : L"", NULL); @@ -1190,6 +1288,37 @@ static void update_backdrop(const struct vo_w32_state *w32) &backdropType, sizeof(backdropType)); } +static void update_cursor_passthrough(const struct vo_w32_state *w32) +{ + if (w32->parent) + return; + + LONG_PTR exstyle = GetWindowLongPtrW(w32->window, GWL_EXSTYLE); + if (exstyle) { + if (w32->opts->cursor_passthrough) { + SetWindowLongPtrW(w32->window, GWL_EXSTYLE, exstyle | WS_EX_LAYERED | WS_EX_TRANSPARENT); + // This is required, otherwise the titlebar disappears. + SetLayeredWindowAttributes(w32->window, 0, 255, LWA_ALPHA); + } else { + SetWindowLongPtrW(w32->window, GWL_EXSTYLE, exstyle & ~(WS_EX_LAYERED | WS_EX_TRANSPARENT)); + } + } +} + +static void set_ime_conversion_mode(const struct vo_w32_state *w32, DWORD mode) +{ + if (w32->parent) + return; + + HIMC imc = ImmGetContext(w32->window); + if (imc) { + DWORD sentence_mode; + if (ImmGetConversionStatus(imc, NULL, &sentence_mode)) + ImmSetConversionStatus(imc, mode, sentence_mode); + ImmReleaseContext(w32->window, imc); + } +} + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -1216,6 +1345,12 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, mp_dispatch_queue_process(w32->dispatch, 0); w32->in_dispatch = false; } + // Start window dragging if the flag is set by the voctrl. + // This is processed here to avoid blocking the dispatch queue. + if (w32->start_dragging) { + w32->start_dragging = false; + begin_dragging(w32); + } switch (message) { case WM_ERASEBKGND: @@ -1241,6 +1376,18 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, case WM_MOVING: { w32->moving = true; RECT *rc = (RECT*)lParam; + // Prevent the window from being moved if the window dragging hack + // is active, and the window is currently in fullscreen. + if (w32->dragging && w32->current_fs) { + // Temporarily disable window arrangement to prevent aero shake + // from being activated. The original system setting will be restored + // after the dragging hack ends. + if (w32->win_arranging) { + SystemParametersInfoW(SPI_SETWINARRANGING, FALSE, 0, 0); + } + *rc = w32->windowrc; + return TRUE; + } if (snap_to_screen_edges(w32, rc)) return TRUE; break; @@ -1344,6 +1491,14 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, w32->window = NULL; PostQuitMessage(0); break; + case WM_COMMAND: { + const char *cmd = mp_win32_menu_get_cmd(w32->menu_ctx, LOWORD(wParam)); + if (cmd) { + mp_cmd_t *cmdt = mp_input_parse_cmd(w32->input_ctx, bstr0(cmd), ""); + mp_input_queue_cmd(w32->input_ctx, cmdt); + } + break; + } case WM_SYSCOMMAND: switch (wParam & 0xFFF0) { case SC_SCREENSAVE: @@ -1404,9 +1559,16 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; case WM_CHAR: case WM_SYSCHAR: - if (handle_char(w32, wParam)) + if (handle_char(w32, wParam, true)) return 0; break; + case WM_UNICHAR: + if (wParam == UNICODE_NOCHAR) { + return TRUE; + } else if (handle_char(w32, wParam, false)) { + return 0; + } + break; case WM_KILLFOCUS: mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL); w32->focused = false; @@ -1431,12 +1593,13 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; case WM_MOUSEMOVE: { if (!w32->tracking) { - w32->tracking = TrackMouseEvent(&w32->trackEvent); + w32->tracking = TrackMouseEvent(&w32->track_event); mp_input_put_key(w32->input_ctx, MP_KEY_MOUSE_ENTER); } // Windows can send spurious mouse events, which would make the mpv // core unhide the mouse cursor on completely unrelated events. See: - // https://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx + // int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (x != w32->mouse_x || y != w32->mouse_y) { @@ -1480,16 +1643,18 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, handle_mouse_down(w32, HIWORD(wParam) == 1 ? MP_MBTN_BACK : MP_MBTN_FORWARD, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - break; + return TRUE; case WM_XBUTTONUP: handle_mouse_up(w32, HIWORD(wParam) == 1 ? MP_MBTN_BACK : MP_MBTN_FORWARD); - break; + return TRUE; case WM_DISPLAYCHANGE: force_update_display_info(w32); break; case WM_SETTINGCHANGE: update_dark_mode(w32); + update_window_style(w32); + update_window_state(w32); break; case WM_NCCALCSIZE: if (!w32->opts->border) @@ -1497,15 +1662,51 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, // Apparently removing WS_CAPTION disables some window animation, instead // just reduce non-client size to remove title bar. if (wParam && lParam && w32->opts->border && !w32->opts->title_bar && - !w32->current_fs && !w32->parent) + !w32->current_fs && !w32->parent && + (GetWindowLongPtrW(w32->window, GWL_STYLE) & WS_CAPTION)) { - ((LPNCCALCSIZE_PARAMS) lParam)->rgrc[0].top -= get_title_bar_height(w32); + // Remove all NC area on Windows 10 due to inability to control the + // top bar height before Windows 11. + if (!check_windows10_build(22000) && !IsMaximized(w32->window)) + return 0; + RECT r = {0}; + adjust_window_rect(w32, w32->window, &r); + NCCALCSIZE_PARAMS *p = (LPNCCALCSIZE_PARAMS)lParam; + p->rgrc[0].top += r.top + get_title_bar_height(w32); + } + break; + case WM_IME_STARTCOMPOSITION: { + HIMC imc = ImmGetContext(w32->window); + if (imc) { + COMPOSITIONFORM cf = {.dwStyle = CFS_POINT, .ptCurrentPos = {0, 0}}; + ImmSetCompositionWindow(imc, &cf); + ImmReleaseContext(w32->window, imc); } break; } + case WM_CREATE: + // The IME can only be changed to alphanumeric input after it's initialized. + // Unfortunately, there is no way to know when this happens, as + // none of the WM_CREATE, WM_INPUTLANGCHANGE, or WM_IME_* messages work. + // This works if the IME is initialized within a short time after + // the window is created. Otherwise, fallback to setting alphanumeric mode on + // the first keypress. + SetTimer(w32->window, (UINT_PTR)WM_CREATE, 250, NULL); + break; + case WM_TIMER: + if (wParam == WM_CREATE) { + // Default to alphanumeric input when the IME is first initialized. + set_ime_conversion_mode(w32, IME_CMODE_ALPHANUMERIC); + return 0; + } + break; + case WM_SHOWMENU: + mp_win32_menu_show(w32->menu_ctx, w32->window); + break; + } - if (message == w32->tbtnCreatedMsg) { - w32->tbtnCreated = true; + if (message == w32->tbtn_created_msg) { + w32->tbtn_created = true; update_playback_state(w32); return 0; } @@ -1609,13 +1810,31 @@ static void remove_parent_hook(struct vo_w32_state *w32) UnhookWinEvent(w32->parent_evt_hook); } +static bool is_key_message(UINT msg) +{ + return msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN || + msg == WM_KEYUP || msg == WM_SYSKEYUP; +} + // Dispatch incoming window events and handle them. // This returns only when the thread is asked to terminate. static void run_message_loop(struct vo_w32_state *w32) { MSG msg; - while (GetMessageW(&msg, 0, 0, 0) > 0) + while (!w32->destroyed && GetMessageW(&msg, 0, 0, 0) > 0) { + // Change the conversion mode on the first keypress, in case the timer + // solution fails. Note that this leaves the mode indicator in the language + // bar showing the original mode until a key is pressed. + if (is_key_message(msg.message) && !w32->conversion_mode_init) { + set_ime_conversion_mode(w32, IME_CMODE_ALPHANUMERIC); + w32->conversion_mode_init = true; + KillTimer(w32->window, (UINT_PTR)WM_CREATE); + } + // Only send IME messages to TranslateMessage + if (is_key_message(msg.message) && msg.wParam == VK_PROCESSKEY) + TranslateMessage(&msg); DispatchMessageW(&msg); + } // Even if the message loop somehow exits, we still have to respond to // external requests until termination is requested. @@ -1623,9 +1842,8 @@ static void run_message_loop(struct vo_w32_state *w32) mp_dispatch_queue_process(w32->dispatch, 1000); } -static void gui_thread_reconfig(void *ptr) +static void window_reconfig(struct vo_w32_state *w32, bool force) { - struct vo_w32_state *w32 = ptr; struct vo *vo = w32->vo; RECT r = get_working_area(w32); @@ -1648,14 +1866,14 @@ static void gui_thread_reconfig(void *ptr) vo_calc_window_geometry3(vo, &screen, &mon, w32->dpi_scale, &geo); vo_apply_window_geometry(vo, &geo); - bool reset_size = (w32->o_dwidth != vo->dwidth || + bool reset_size = ((w32->o_dwidth != vo->dwidth || w32->o_dheight != vo->dheight) && - w32->opts->auto_window_resize; + w32->opts->auto_window_resize) || force; w32->o_dwidth = vo->dwidth; w32->o_dheight = vo->dheight; - if (!w32->parent && !w32->window_bounds_initialized) { + if (!w32->parent && (!w32->window_bounds_initialized || force)) { SetRect(&w32->windowrc, geo.win.x0, geo.win.y0, geo.win.x0 + vo->dwidth, geo.win.y0 + vo->dheight); w32->prev_windowrc = w32->windowrc; @@ -1686,6 +1904,11 @@ finish: reinit_window_state(w32); } +static void gui_thread_reconfig(void *ptr) +{ + window_reconfig(ptr, false); +} + // Resize the window. On the first call, it's also made visible. void vo_w32_config(struct vo *vo) { @@ -1704,26 +1927,15 @@ static void w32_api_load(struct vo_w32_state *w32) // Available since Win10 w32->api.pAdjustWindowRectExForDpi = !user32_dll ? NULL : (void *)GetProcAddress(user32_dll, "AdjustWindowRectExForDpi"); - - // 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"); + w32->api.pGetSystemMetricsForDpi = !user32_dll ? NULL : + (void *)GetProcAddress(user32_dll, "GetSystemMetricsForDpi"); // Dark mode related functions, available since the 1809 Windows 10 update // Check the Windows build version as on previous versions used ordinals // may point to unexpected code/data. Alternatively could check uxtheme.dll // version directly, but it is little bit more boilerplate code, and build // number is good enough check. - void (WINAPI *pRtlGetNtVersionNumbers)(LPDWORD, LPDWORD, LPDWORD) = - (void *)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers"); - - DWORD major, build; - pRtlGetNtVersionNumbers(&major, NULL, &build); - build &= ~0xF0000000; - - HMODULE uxtheme_dll = (major < 10 || build < 17763) ? NULL : + HMODULE uxtheme_dll = !check_windows10_build(17763) ? NULL : GetModuleHandle(L"uxtheme.dll"); w32->api.pShouldAppsUseDarkMode = !uxtheme_dll ? NULL : (void *)GetProcAddress(uxtheme_dll, MAKEINTRESOURCEA(132)); @@ -1741,10 +1953,6 @@ static MP_THREAD_VOID gui_thread(void *ptr) w32_api_load(w32); - // 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); @@ -1776,6 +1984,8 @@ static MP_THREAD_VOID gui_thread(void *ptr) update_affinity(w32); if (w32->opts->backdrop_type) update_backdrop(w32); + if (w32->opts->cursor_passthrough) + update_cursor_passthrough(w32); if (SUCCEEDED(OleInitialize(NULL))) { ole_ok = true; @@ -1805,7 +2015,7 @@ static MP_THREAD_VOID gui_thread(void *ptr) ITaskbarList3_Release(w32->taskbar_list3); w32->taskbar_list3 = NULL; } else { - w32->tbtnCreatedMsg = RegisterWindowMessage(L"TaskbarButtonCreated"); + w32->tbtn_created_msg = RegisterWindowMessage(L"TaskbarButtonCreated"); } } } else { @@ -1813,7 +2023,7 @@ static MP_THREAD_VOID gui_thread(void *ptr) } w32->tracking = FALSE; - w32->trackEvent = (TRACKMOUSEEVENT){ + w32->track_event = (TRACKMOUSEEVENT){ .cbSize = sizeof(TRACKMOUSEEVENT), .dwFlags = TME_LEAVE, .hwndTrack = w32->window, @@ -1866,6 +2076,7 @@ bool vo_w32_init(struct vo *vo) .dispatch = mp_dispatch_create(w32), }; w32->opts = w32->opts_cache->opts; + w32->menu_ctx = mp_win32_menu_init(); vo->w32 = w32; if (mp_thread_create(&w32->thread, gui_thread, w32)) @@ -1949,6 +2160,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) struct mp_vo_opts *vo_opts = w32->opts_cache->opts; if (changed_option == &vo_opts->fullscreen) { + if (!vo_opts->fullscreen) + update_maximized_state(w32, true); reinit_window_state(w32); } else if (changed_option == &vo_opts->window_affinity) { update_affinity(w32); @@ -1956,6 +2169,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) update_window_state(w32); } else if (changed_option == &vo_opts->backdrop_type) { update_backdrop(w32); + } else if (changed_option == &vo_opts->cursor_passthrough) { + update_cursor_passthrough(w32); } else if (changed_option == &vo_opts->border || changed_option == &vo_opts->title_bar) { @@ -1964,9 +2179,16 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) } else if (changed_option == &vo_opts->window_minimized) { update_minimized_state(w32); } else if (changed_option == &vo_opts->window_maximized) { - update_maximized_state(w32); + update_maximized_state(w32, false); } else if (changed_option == &vo_opts->window_corners) { update_corners_pref(w32); + } else if (changed_option == &vo_opts->geometry || changed_option == &vo_opts->autofit || + changed_option == &vo_opts->autofit_smaller || changed_option == &vo_opts->autofit_larger) + { + if (w32->opts->window_maximized) { + w32->unmaximize = true; + } + window_reconfig(w32, true); } } @@ -1989,8 +2211,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) return VO_FALSE; RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc; - s[0] = rect_w(*rc) / w32->dpi_scale; - s[1] = rect_h(*rc) / w32->dpi_scale; + s[0] = rect_w(*rc); + s[1] = rect_h(*rc); return VO_TRUE; } case VOCTRL_SET_UNFS_WINDOW_SIZE: { @@ -1999,12 +2221,12 @@ 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->dpi_scale; - s[1] *= w32->dpi_scale; - RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc; resize_and_move_rect(w32, rc, s[0], s[1]); + if (w32->opts->window_maximized) { + w32->unmaximize = true; + } w32->fit_on_screen = true; reinit_window_state(w32); return VO_TRUE; @@ -2064,6 +2286,15 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) case VOCTRL_GET_FOCUSED: *(bool *)arg = w32->focused; return VO_TRUE; + case VOCTRL_BEGIN_DRAGGING: + w32->start_dragging = true; + return VO_TRUE; + case VOCTRL_SHOW_MENU: + PostMessageW(w32->window, WM_SHOWMENU, 0, 0); + return VO_TRUE; + case VOCTRL_UPDATE_MENU: + mp_win32_menu_update(w32->menu_ctx, (struct mpv_node *)arg); + return VO_TRUE; } return VO_NOTIMPL; } @@ -2127,6 +2358,7 @@ void vo_w32_uninit(struct vo *vo) AvRevertMmThreadCharacteristics(w32->avrt_handle); + mp_win32_menu_uninit(w32->menu_ctx); talloc_free(w32); vo->w32 = NULL; } @@ -2142,3 +2374,24 @@ void vo_w32_run_on_thread(struct vo *vo, void (*cb)(void *ctx), void *ctx) struct vo_w32_state *w32 = vo->w32; mp_dispatch_run(w32->dispatch, cb, ctx); } + +void vo_w32_set_transparency(struct vo *vo, bool enable) +{ + struct vo_w32_state *w32 = vo->w32; + if (w32->parent) + return; + + DWM_BLURBEHIND dbb = {0}; + if (enable) { + HRGN rgn = CreateRectRgn(0, 0, -1, -1); + dbb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + dbb.hRgnBlur = rgn; + dbb.fEnable = TRUE; + DwmEnableBlurBehindWindow(w32->window, &dbb); + DeleteObject(rgn); + } else { + dbb.dwFlags = DWM_BB_ENABLE; + dbb.fEnable = FALSE; + DwmEnableBlurBehindWindow(w32->window, &dbb); + } +} diff --git a/video/out/w32_common.h b/video/out/w32_common.h index 528b216..bc22a2d 100644 --- a/video/out/w32_common.h +++ b/video/out/w32_common.h @@ -32,5 +32,6 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg); void vo_w32_config(struct vo *vo); HWND vo_w32_hwnd(struct vo *vo); void vo_w32_run_on_thread(struct vo *vo, void (*cb)(void *ctx), void *ctx); +void vo_w32_set_transparency(struct vo *vo, bool enable); #endif /* MPLAYER_W32_COMMON_H */ diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 589135f..4a86c21 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -56,6 +56,10 @@ #include "cursor-shape-v1.h" #endif +#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 21 +#define HAVE_WAYLAND_1_21 +#endif + #if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 22 #define HAVE_WAYLAND_1_22 #endif @@ -133,7 +137,8 @@ static const struct mp_keymap keymap[] = { {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}, - {XKB_KEY_XF86Back, MP_KEY_BACK}, {XKB_KEY_XF86Tools, MP_KEY_TOOLS}, + {XKB_KEY_XF86Back, MP_KEY_GO_BACK}, {XKB_KEY_XF86Forward, MP_KEY_GO_FORWARD}, + {XKB_KEY_XF86Tools, MP_KEY_TOOLS}, {XKB_KEY_XF86ZoomIn, MP_KEY_ZOOMIN}, {XKB_KEY_XF86ZoomOut, MP_KEY_ZOOMOUT}, {0, 0} @@ -182,53 +187,93 @@ struct vo_wayland_output { struct wl_list link; }; +struct vo_wayland_seat { + struct vo_wayland_state *wl; + struct wl_seat *seat; + uint32_t id; + struct wl_keyboard *keyboard; + struct wl_pointer *pointer; + struct wl_touch *touch; + struct wl_data_device *dnd_ddev; + /* TODO: unvoid this if required wayland protocols is bumped to 1.32+ */ + void *cursor_shape_device; + uint32_t pointer_enter_serial; + uint32_t pointer_button_serial; + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + uint32_t keyboard_code; + int mpkey; + int mpmod; + double axis_value_vertical; + int32_t axis_value120_vertical; + double axis_value_horizontal; + int32_t axis_value120_horizontal; + bool axis_value120_scroll; + bool has_keyboard_input; + struct wl_list link; +}; + +static bool single_output_spanned(struct vo_wayland_state *wl); + static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels, enum xdg_toplevel_resize_edge *edge); -static int get_mods(struct vo_wayland_state *wl); +static int get_mods(struct vo_wayland_seat *seat); +static int greatest_common_divisor(int a, int b); static int lookupkey(int key); -static int set_cursor_visibility(struct vo_wayland_state *wl, bool on); +static int set_cursor_visibility(struct vo_wayland_seat *s, bool on); static int spawn_cursor(struct vo_wayland_state *wl); static void add_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback); -static void get_shape_device(struct vo_wayland_state *wl); -static int greatest_common_divisor(int a, int b); +static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height); +static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s); static void guess_focus(struct vo_wayland_state *wl); -static void prepare_resize(struct vo_wayland_state *wl, int width, int height); +static void prepare_resize(struct vo_wayland_state *wl); static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback); static void remove_output(struct vo_wayland_output *out); +static void remove_seat(struct vo_wayland_seat *seat); static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode); static void rescale_geometry(struct vo_wayland_state *wl, double old_scale); static void set_geometry(struct vo_wayland_state *wl, bool resize); static void set_surface_scaling(struct vo_wayland_state *wl); -static void window_move(struct vo_wayland_state *wl, uint32_t serial); +static void update_output_scaling(struct vo_wayland_state *wl); +static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, + struct mp_rect old_output_geometry); /* Wayland listener boilerplate */ 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; - - wl->pointer = pointer; - wl->pointer_id = serial; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; - set_cursor_visibility(wl, wl->cursor_visible); + s->pointer_enter_serial = serial; + set_cursor_visibility(s, wl->cursor_visible); mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER); + + wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling; + wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling; + + if (!wl->toplevel_configured) + mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y); + wl->toplevel_configured = false; } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling; wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling; @@ -242,7 +287,8 @@ 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP; @@ -272,44 +318,106 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, } if (button) - mp_input_put_key(wl->vo->input_ctx, button | state | wl->mpmod); + mp_input_put_key(wl->vo->input_ctx, button | state | s->mpmod); + enum xdg_toplevel_resize_edge edges; if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && - !wl->locked_size && (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN)) + !wl->locked_size && (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN) && + !wl->vo_opts->border && check_for_resize(wl, wl->opts->edge_pixels_pointer, &edges)) { - uint32_t edges; // Implement an edge resize zone if there are no decorations - if (!wl->vo_opts->border && check_for_resize(wl, wl->opts->edge_pixels_pointer, &edges)) { - xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edges); - } else { - window_move(wl, serial); - } - // Explicitly send an UP event after the client finishes a move/resize + xdg_toplevel_resize(wl->xdg_toplevel, s->seat, serial, edges); + // Explicitly send an UP event after the client finishes a resize mp_input_put_key(wl->vo->input_ctx, button | MP_KEY_STATE_UP); + } else if (state == MP_KEY_STATE_DOWN) { + // Save the serial and seat for voctrl-initialized dragging requests. + s->pointer_button_serial = serial; + wl->last_button_seat = s; + } else { + wl->last_button_seat = NULL; } } 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; + struct vo_wayland_seat *s = data; + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + s->axis_value_vertical += wl_fixed_to_double(value); + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + s->axis_value_horizontal += wl_fixed_to_double(value); + break; + } +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) +{ + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; + double value_vertical, value_horizontal; + if (s->axis_value120_scroll) { + // Prefer axis_value120 if supported and the axis event is from mouse wheel. + value_vertical = s->axis_value120_vertical / 120.0; + value_horizontal = s->axis_value120_horizontal / 120.0; + } else { + // The axis value is specified in logical coordinates, but the exact value emitted + // by one mouse wheel click is unspecified. In practice, most compositors use either + // 10 (GNOME, Weston) or 15 (wlroots, same as libinput) as the value. + // Divide the value by 10 and clamp it between -1 and 1 so that mouse wheel clicks + // work as intended on all compositors while still allowing high resolution trackpads. + value_vertical = MPCLAMP(s->axis_value_vertical / 10.0, -1, 1); + value_horizontal = MPCLAMP(s->axis_value_horizontal / 10.0, -1, 1); + } + + if (value_vertical > 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | s->mpmod, +value_vertical); + if (value_vertical < 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | s->mpmod, -value_vertical); + if (value_horizontal > 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | s->mpmod, +value_horizontal); + if (value_horizontal < 0) + mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | s->mpmod, -value_horizontal); + + s->axis_value120_scroll = false; + s->axis_value_vertical = 0; + s->axis_value_horizontal = 0; + s->axis_value120_vertical = 0; + s->axis_value120_horizontal = 0; +} + +static void pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) +{ +} + +static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ +} + +static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) +{ +} - double val = wl_fixed_to_double(value) < 0 ? -1 : 1; +#ifdef HAVE_WAYLAND_1_21 +static void pointer_handle_axis_value120(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t value120) +{ + struct vo_wayland_seat *s = data; + s->axis_value120_scroll = true; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: - if (value > 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | wl->mpmod, +val); - if (value < 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | wl->mpmod, -val); + s->axis_value120_vertical += value120; break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: - if (value > 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | wl->mpmod, +val); - if (value < 0) - mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | wl->mpmod, -val); + s->axis_value120_horizontal += value120; break; } } +#endif static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, @@ -317,40 +425,55 @@ static const struct wl_pointer_listener pointer_listener = { pointer_handle_motion, pointer_handle_button, pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, +#ifdef HAVE_WAYLAND_1_21 + pointer_handle_axis_value120, +#endif }; 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_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling; wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling; - enum xdg_toplevel_resize_edge edge; - if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y)) { - if (check_for_resize(wl, wl->opts->edge_pixels_touch, &edge)) { - xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edge); - } else { - xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); - } - } - 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); + + enum xdg_toplevel_resize_edge edge; + if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && + !wl->locked_size && check_for_resize(wl, wl->opts->edge_pixels_touch, &edge)) + { + xdg_toplevel_resize(wl->xdg_toplevel, s->seat, serial, edge); + // Explicitly send an UP event after the client finishes a resize + mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); + } else { + // Save the serial and seat for voctrl-initialized dragging requests. + s->pointer_button_serial = serial; + wl->last_button_seat = s; + } } 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP); + wl->last_button_seat = NULL; } 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling; wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling; @@ -366,18 +489,31 @@ static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { } +static void touch_handle_shape(void *data, struct wl_touch *wl_touch, + int32_t id, wl_fixed_t major, wl_fixed_t minor) +{ +} + +static void touch_handle_orientation(void *data, struct wl_touch *wl_touch, + int32_t id, wl_fixed_t orientation) +{ +} + static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, + touch_handle_shape, + touch_handle_orientation, }; 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; char *map_str; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { @@ -391,23 +527,25 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, return; } - wl->xkb_keymap = xkb_keymap_new_from_buffer(wl->xkb_context, map_str, - strnlen(map_str, size), - XKB_KEYMAP_FORMAT_TEXT_V1, 0); + if (!s->xkb_keymap) + s->xkb_keymap = xkb_keymap_new_from_buffer(wl->xkb_context, map_str, + strnlen(map_str, size), + XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_str, size); close(fd); - if (!wl->xkb_keymap) { + if (!s->xkb_keymap) { MP_ERR(wl, "failed to compile keymap\n"); return; } - wl->xkb_state = xkb_state_new(wl->xkb_keymap); - if (!wl->xkb_state) { + if (!s->xkb_state) + s->xkb_state = xkb_state_new(s->xkb_keymap); + if (!s->xkb_state) { MP_ERR(wl, "failed to create XKB state\n"); - xkb_keymap_unref(wl->xkb_keymap); - wl->xkb_keymap = NULL; + xkb_keymap_unref(s->xkb_keymap); + s->xkb_keymap = NULL; return; } } @@ -416,19 +554,21 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - struct vo_wayland_state *wl = data; - wl->has_keyboard_input = true; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; + s->has_keyboard_input = true; guess_focus(wl); } static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { - struct vo_wayland_state *wl = data; - wl->has_keyboard_input = false; - wl->keyboard_code = 0; - wl->mpkey = 0; - wl->mpmod = 0; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; + s->has_keyboard_input = false; + s->keyboard_code = 0; + s->mpkey = 0; + s->mpmod = 0; mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); guess_focus(wl); } @@ -437,30 +577,37 @@ 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; - wl->keyboard_code = key + 8; - xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->xkb_state, wl->keyboard_code); + s->keyboard_code = key + 8; + xkb_keysym_t sym = xkb_state_key_get_one_sym(s->xkb_state, s->keyboard_code); int mpkey = lookupkey(sym); state = state == WL_KEYBOARD_KEY_STATE_PRESSED ? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP; if (mpkey) { - mp_input_put_key(wl->vo->input_ctx, mpkey | state | wl->mpmod); + mp_input_put_key(wl->vo->input_ctx, mpkey | state | s->mpmod); } else { - char s[128]; - if (xkb_keysym_to_utf8(sym, s, sizeof(s)) > 0) { - mp_input_put_key_utf8(wl->vo->input_ctx, state | wl->mpmod, bstr0(s)); + char str[128]; + if (xkb_keysym_to_utf8(sym, str, sizeof(str)) > 0) { + mp_input_put_key_utf8(wl->vo->input_ctx, state | s->mpmod, bstr0(str)); } else { // Assume a modifier was pressed and handle it in the mod event instead. + // If a modifier is released before a regular key, also release that + // key to not activate it again by accident. + if (state == MP_KEY_STATE_UP) { + s->mpkey = 0; + mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); + } return; } } if (state == MP_KEY_STATE_DOWN) - wl->mpkey = mpkey; + s->mpkey = mpkey; if (mpkey && state == MP_KEY_STATE_UP) - wl->mpkey = 0; + s->mpkey = 0; } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, @@ -468,21 +615,23 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; - if (wl->xkb_state) { - xkb_state_update_mask(wl->xkb_state, mods_depressed, mods_latched, + if (s->xkb_state) { + xkb_state_update_mask(s->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); - wl->mpmod = get_mods(wl); - if (wl->mpkey) - mp_input_put_key(wl->vo->input_ctx, wl->mpkey | MP_KEY_STATE_DOWN | wl->mpmod); + s->mpmod = get_mods(s); + if (s->mpkey) + mp_input_put_key(wl->vo->input_ctx, s->mpkey | MP_KEY_STATE_DOWN | s->mpmod); } } 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->vo_opts->native_keyrepeat) mp_input_set_repeat_info(wl->vo->input_ctx, rate, delay); } @@ -499,43 +648,50 @@ static const struct wl_keyboard_listener keyboard_listener = { static void seat_handle_caps(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->pointer) { - wl->pointer = wl_seat_get_pointer(seat); - get_shape_device(wl); - 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; + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !s->pointer) { + s->pointer = wl_seat_get_pointer(seat); + get_shape_device(s->wl, s); + wl_pointer_add_listener(s->pointer, &pointer_listener, s); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && s->pointer) { + wl_pointer_destroy(s->pointer); + s->pointer = NULL; } - 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_KEYBOARD) && !s->keyboard) { + s->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(s->keyboard, &keyboard_listener, s); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && s->keyboard) { + wl_keyboard_destroy(s->keyboard); + s->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; + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !s->touch) { + s->touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(s->touch, s); + wl_touch_add_listener(s->touch, &touch_listener, s); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && s->touch) { + wl_touch_destroy(s->touch); + s->touch = NULL; } } +static void seat_handle_name(void *data, struct wl_seat *seat, + const char *name) +{ +} + static const struct wl_seat_listener seat_listener = { seat_handle_caps, + seat_handle_name, }; static void data_offer_handle_offer(void *data, struct wl_data_offer *offer, const char *mime_type) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type); if (score > wl->dnd_mime_score && wl->vo_opts->drag_and_drop != -2) { wl->dnd_mime_score = score; @@ -552,7 +708,8 @@ static void data_offer_source_actions(void *data, struct wl_data_offer *offer, u static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (dnd_action && wl->vo_opts->drag_and_drop != -2) { if (wl->vo_opts->drag_and_drop >= 0) { wl->dnd_action = wl->vo_opts->drag_and_drop; @@ -560,8 +717,14 @@ static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, u 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 char * const dnd_action_names[] = { + [DND_REPLACE] = "DND_REPLACE", + [DND_APPEND] = "DND_APPEND", + [DND_INSERT_NEXT] = "DND_INSERT_NEXT", + }; + + MP_VERBOSE(wl, "DND action is %s\n", dnd_action_names[wl->dnd_action]); } } @@ -574,12 +737,13 @@ static const struct wl_data_offer_listener data_offer_listener = { 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; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) wl_data_offer_destroy(wl->dnd_offer); wl->dnd_offer = id; - wl_data_offer_add_listener(id, &data_offer_listener, wl); + wl_data_offer_add_listener(id, &data_offer_listener, s); } static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, @@ -587,7 +751,8 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer != id) { MP_FATAL(wl, "DND offer ID mismatch!\n"); return; @@ -605,7 +770,8 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) { if (wl->dnd_fd != -1) @@ -625,13 +791,15 @@ static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev) 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) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; wl_data_offer_accept(wl->dnd_offer, time, wl->dnd_mime_type); } static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; int pipefd[2]; @@ -652,7 +820,8 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev) static void data_device_handle_selection(void *data, struct wl_data_device *wl_ddev, struct wl_data_offer *id) { - struct vo_wayland_state *wl = data; + struct vo_wayland_seat *s = data; + struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) { wl_data_offer_destroy(wl->dnd_offer); @@ -723,10 +892,8 @@ static void output_handle_done(void *data, struct wl_output *wl_output) * geometry and scaling should be recalculated. */ if (wl->current_output && wl->current_output->output == wl_output) { set_surface_scaling(wl); - spawn_cursor(wl); set_geometry(wl, false); - prepare_resize(wl, 0, 0); - wl->pending_vo_events |= VO_EVENT_DPI; + prepare_resize(wl); } wl->pending_vo_events |= VO_EVENT_WIN_STATE; @@ -775,36 +942,23 @@ static void surface_handle_enter(void *data, struct wl_surface *wl_surface, struct mp_rect old_geometry = wl->geometry; wl->current_output = NULL; + int outputs = 0; struct vo_wayland_output *o; wl_list_for_each(o, &wl->output_list, link) { if (o->output == output) { wl->current_output = o; - break; + wl->current_output->has_surface = true; } + if (o->has_surface) + ++outputs; } - wl->current_output->has_surface = true; - bool force_resize = false; - - if (!wl->fractional_scale_manager && wl_surface_get_version(wl_surface) < 6 && - wl->scaling != wl->current_output->scale) - { - set_surface_scaling(wl); - spawn_cursor(wl); - force_resize = true; - wl->pending_vo_events |= VO_EVENT_DPI; - } - - if (!mp_rect_equals(&old_output_geometry, &wl->current_output->geometry)) { - set_geometry(wl, false); - force_resize = true; - } - - if (!mp_rect_equals(&old_geometry, &wl->geometry) || force_resize) - prepare_resize(wl, 0, 0); + if (outputs == 1) + update_output_geometry(wl, old_geometry, old_output_geometry); MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %f, refresh rate = %f Hz\n", - o->make, o->model, o->id, wl->scaling, o->refresh_rate); + wl->current_output->make, wl->current_output->model, + wl->current_output->id, wl->scaling, wl->current_output->refresh_rate); wl->pending_vo_events |= VO_EVENT_WIN_STATE; } @@ -813,15 +967,27 @@ static void surface_handle_leave(void *data, struct wl_surface *wl_surface, struct wl_output *output) { struct vo_wayland_state *wl = data; + if (!wl->current_output) + return; + struct mp_rect old_output_geometry = wl->current_output->geometry; + struct mp_rect old_geometry = wl->geometry; + + int outputs = 0; struct vo_wayland_output *o; wl_list_for_each(o, &wl->output_list, link) { - if (o->output == output) { + if (o->output == output) o->has_surface = false; - wl->pending_vo_events |= VO_EVENT_WIN_STATE; - return; - } + if (o->has_surface) + ++outputs; + if (o->output != output && o->has_surface) + wl->current_output = o; } + + if (outputs == 1) + update_output_geometry(wl, old_geometry, old_output_geometry); + + wl->pending_vo_events |= VO_EVENT_WIN_STATE; } #ifdef HAVE_WAYLAND_1_22 @@ -830,21 +996,20 @@ static void surface_handle_preferred_buffer_scale(void *data, int32_t scale) { struct vo_wayland_state *wl = data; - double old_scale = wl->scaling; - if (wl->fractional_scale_manager) + if (wl->fractional_scale_manager || wl->scaling == scale) return; - // dmabuf_wayland is always wl->scaling = 1 - wl->scaling = !wl->using_dmabuf_wayland ? scale : 1; + wl->pending_scaling = scale; + wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", wl->scaling); wl->pending_vo_events |= VO_EVENT_DPI; - if (wl->current_output) { - rescale_geometry(wl, old_scale); - set_geometry(wl, false); - prepare_resize(wl, 0, 0); - } + wl->need_rescale = true; + + // Update scaling now. + if (single_output_spanned(wl)) + update_output_scaling(wl); } static void surface_handle_preferred_buffer_transform(void *data, @@ -889,12 +1054,12 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, struct mp_vo_opts *vo_opts = wl->vo_opts; struct mp_rect old_geometry = wl->geometry; - int old_toplevel_width = wl->toplevel_width; - int old_toplevel_height = wl->toplevel_height; - wl->toplevel_width = width; - wl->toplevel_height = height; + if (width < 0 || height < 0) { + MP_WARN(wl, "Compositor sent negative width/height values. Treating them as zero.\n"); + width = height = 0; + } - if (!wl->configured) { + if (!wl->geometry_configured) { /* Save initial window size if the compositor gives us a hint here. */ bool autofit_or_geometry = vo_opts->geometry.wh_valid || vo_opts->autofit.wh_valid || vo_opts->autofit_larger.wh_valid || vo_opts->autofit_smaller.wh_valid; @@ -949,20 +1114,24 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, wl->hidden = is_suspended; if (vo_opts->fullscreen != is_fullscreen) { - wl->state_change = true; + wl->state_change = wl->reconfigured; vo_opts->fullscreen = is_fullscreen; m_config_cache_write_opt(wl->vo_opts_cache, &vo_opts->fullscreen); } if (vo_opts->window_maximized != is_maximized) { - wl->state_change = true; + wl->state_change = wl->reconfigured; vo_opts->window_maximized = is_maximized; m_config_cache_write_opt(wl->vo_opts_cache, &vo_opts->window_maximized); } + if (!is_tiled && wl->tiled) + wl->state_change = wl->reconfigured; + wl->tiled = is_tiled; wl->locked_size = is_fullscreen || is_maximized || is_tiled; + wl->reconfigured = false; if (wl->requested_decoration) request_decoration_mode(wl, wl->requested_decoration); @@ -993,26 +1162,17 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, goto resize; } - if (old_toplevel_width == wl->toplevel_width && - old_toplevel_height == wl->toplevel_height) - return; - if (!wl->locked_size) { - if (vo_opts->keepaspect) { - double scale_factor = (double)width / wl->reduced_width; - width = ceil(wl->reduced_width * scale_factor); - if (vo_opts->keepaspect_window) - height = ceil(wl->reduced_height * scale_factor); - } + apply_keepaspect(wl, &width, &height); wl->window_size.x0 = 0; wl->window_size.y0 = 0; - wl->window_size.x1 = round(width * wl->scaling); - wl->window_size.y1 = round(height * wl->scaling); + wl->window_size.x1 = lround(width * wl->scaling); + wl->window_size.y1 = lround(height * wl->scaling); } wl->geometry.x0 = 0; wl->geometry.y0 = 0; - wl->geometry.x1 = round(width * wl->scaling); - wl->geometry.y1 = round(height * wl->scaling); + wl->geometry.x1 = lround(width * wl->scaling); + wl->geometry.y1 = lround(height * wl->scaling); if (mp_rect_equals(&old_geometry, &wl->geometry)) return; @@ -1022,7 +1182,7 @@ resize: mp_rect_w(old_geometry), mp_rect_h(old_geometry), mp_rect_w(wl->geometry), mp_rect_h(wl->geometry)); - prepare_resize(wl, width, height); + prepare_resize(wl); wl->toplevel_configured = true; } @@ -1062,18 +1222,19 @@ static void preferred_scale(void *data, uint32_t scale) { struct vo_wayland_state *wl = data; - double old_scale = wl->scaling; + double new_scale = (double)scale / 120; + if (wl->scaling == new_scale) + return; - // dmabuf_wayland is always wl->scaling = 1 - wl->scaling = !wl->using_dmabuf_wayland ? (double)scale / 120 : 1; + wl->pending_scaling = new_scale; + wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", - wl->scaling); - wl->pending_vo_events |= VO_EVENT_DPI; - if (wl->current_output) { - rescale_geometry(wl, old_scale); - set_geometry(wl, false); - prepare_resize(wl, 0, 0); - } + wl->pending_scaling); + wl->need_rescale = true; + + // Update scaling now. + if (single_output_spanned(wl)) + update_output_scaling(wl); } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { @@ -1296,9 +1457,8 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id wl_surface_add_listener(wl->surface, &surface_listener, wl); } - if (!strcmp(interface, wl_subcompositor_interface.name) && (ver >= 1) && found++) { + if (!strcmp(interface, wl_subcompositor_interface.name) && (ver >= 1) && found++) wl->subcompositor = wl_registry_bind(reg, id, &wl_subcompositor_interface, 1); - } if (!strcmp (interface, zwp_linux_dmabuf_v1_interface.name) && (ver >= 4) && found++) { wl->dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, 4); @@ -1306,13 +1466,11 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id zwp_linux_dmabuf_feedback_v1_add_listener(wl->dmabuf_feedback, &dmabuf_feedback_listener, wl); } - if (!strcmp (interface, wp_viewporter_interface.name) && (ver >= 1) && found++) { - wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, 1); - } + if (!strcmp (interface, wp_viewporter_interface.name) && (ver >= 1) && found++) + wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, 1); - if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) { + 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 (!strcmp(interface, wl_output_interface.name) && (ver >= 2) && found++) { struct vo_wayland_output *output = talloc_zero(wl, struct vo_wayland_output); @@ -1329,34 +1487,41 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id } 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 (ver < 5) + MP_WARN(wl, "Scrolling won't work because the compositor doesn't " + "support version 5 of wl_seat protocol!\n"); +#ifdef HAVE_WAYLAND_1_21 + ver = MPMIN(ver, 8); /* Cap at 8 in case new events are added later. */ +#else + ver = MPMIN(ver, 7); +#endif + struct vo_wayland_seat *seat = talloc_zero(wl, struct vo_wayland_seat); + seat->wl = wl; + seat->id = id; + seat->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver); + wl_seat_add_listener(seat->seat, &seat_listener, seat); + wl_list_insert(&wl->seat_list, &seat->link); } - if (!strcmp(interface, wl_shm_interface.name) && found++) { + if (!strcmp(interface, wl_shm_interface.name) && found++) wl->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1); - } #if HAVE_WAYLAND_PROTOCOLS_1_27 - if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) wl->content_type_manager = wl_registry_bind(reg, id, &wp_content_type_manager_v1_interface, 1); - } - if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) wl->single_pixel_manager = wl_registry_bind(reg, id, &wp_single_pixel_buffer_manager_v1_interface, 1); - } #endif #if HAVE_WAYLAND_PROTOCOLS_1_31 - if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) wl->fractional_scale_manager = wl_registry_bind(reg, id, &wp_fractional_scale_manager_v1_interface, 1); - } #endif #if HAVE_WAYLAND_PROTOCOLS_1_32 - if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name) && found++) { + if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name) && found++) wl->cursor_shape_manager = wl_registry_bind(reg, id, &wp_cursor_shape_manager_v1_interface, 1); - } #endif if (!strcmp(interface, wp_presentation_interface.name) && found++) { @@ -1370,13 +1535,11 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id xdg_wm_base_add_listener(wl->wm_base, &xdg_wm_base_listener, wl); } - if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name) && found++) { + if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name) && found++) wl->xdg_decoration_manager = wl_registry_bind(reg, id, &zxdg_decoration_manager_v1_interface, 1); - } - if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) { + 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); @@ -1385,13 +1548,21 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id 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) { + struct vo_wayland_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &wl->output_list, link) { if (output->id == id) { remove_output(output); return; } } + + struct vo_wayland_seat *seat, *seat_tmp; + wl_list_for_each_safe(seat, seat_tmp, &wl->seat_list, link) { + if (seat->id == id) { + remove_seat(seat); + return; + } + } } static const struct wl_registry_listener registry_listener = { @@ -1400,6 +1571,26 @@ static const struct wl_registry_listener registry_listener = { }; /* Static functions */ +static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height) +{ + if (!wl->vo_opts->keepaspect) + return; + + double scale_factor = (double)*width / wl->reduced_width; + *width = ceil(wl->reduced_width * scale_factor); + if (wl->vo_opts->keepaspect_window) + *height = ceil(wl->reduced_height * scale_factor); +} + +static void free_dnd_data(struct vo_wayland_state *wl) +{ + // caller should close wl->dnd_fd if appropriate + + wl->dnd_action = -1; + TA_FREEP(&wl->dnd_mime_type); + wl->dnd_mime_score = 0; +} + static void check_dnd_fd(struct vo_wayland_state *wl) { if (wl->dnd_fd == -1) @@ -1410,40 +1601,44 @@ static void check_dnd_fd(struct vo_wayland_state *wl) return; 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; + ssize_t data_read = 0; + const size_t chunk_size = 256; + bstr file_list = { + .start = talloc_zero_size(NULL, chunk_size), + }; + + while (1) { + data_read = read(wl->dnd_fd, file_list.start + file_list.len, chunk_size); + if (data_read == -1 && errno == EINTR) + continue; + else if (data_read <= 0) + break; + file_list.len += data_read; + file_list.start = talloc_realloc_size(NULL, file_list.start, file_list.len + chunk_size); + memset(file_list.start + file_list.len, 0, chunk_size); } - MP_VERBOSE(wl, "Read %td bytes from the DND fd\n", offset); + if (data_read == -1) { + MP_VERBOSE(wl, "DND aborted (read error)\n"); + } else { + MP_VERBOSE(wl, "Read %zu bytes from the DND fd\n", file_list.len); - 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: - if (wl->dnd_mime_type) - talloc_free(wl->dnd_mime_type); + if (wl->dnd_offer) + wl_data_offer_finish(wl->dnd_offer); - if (wl->dnd_action >= 0 && wl->dnd_offer) - wl_data_offer_finish(wl->dnd_offer); + assert(wl->dnd_action >= 0); + mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type, + file_list, wl->dnd_action); + } - wl->dnd_action = -1; - wl->dnd_mime_type = NULL; - wl->dnd_mime_score = 0; + talloc_free(file_list.start); + free_dnd_data(wl); } if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) { + if (wl->dnd_action >= 0) + MP_VERBOSE(wl, "DND aborted (hang up or error)\n"); + free_dnd_data(wl); close(wl->dnd_fd); wl->dnd_fd = -1; } @@ -1499,13 +1694,12 @@ static bool create_input(struct vo_wayland_state *wl) static int create_viewports(struct vo_wayland_state *wl) { - if (wl->viewporter) { - wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface); - wl->osd_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->osd_surface); - wl->video_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->video_surface); - } + wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface); + wl->cursor_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->cursor_surface); + wl->osd_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->osd_surface); + wl->video_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->video_surface); - if (wl->viewporter && (!wl->viewport || !wl->osd_viewport || !wl->video_viewport)) { + if (!wl->viewport || !wl->osd_viewport || !wl->video_viewport) { MP_ERR(wl, "failed to create viewport interfaces!\n"); return 1; } @@ -1544,8 +1738,6 @@ static void add_feedback(struct vo_wayland_feedback_pool *fback_pool, static void do_minimize(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; if (wl->vo_opts->window_minimized) xdg_toplevel_set_minimized(wl->xdg_toplevel); } @@ -1566,7 +1758,7 @@ static char **get_displays_spanned(struct vo_wayland_state *wl) return names; } -static int get_mods(struct vo_wayland_state *wl) +static int get_mods(struct vo_wayland_seat *s) { static char* const mod_names[] = { XKB_MOD_NAME_SHIFT, @@ -1585,21 +1777,21 @@ static int get_mods(struct vo_wayland_state *wl) int modifiers = 0; for (int n = 0; n < MP_ARRAY_SIZE(mods); n++) { - 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, wl->keyboard_code, index) - && xkb_state_mod_index_is_active(wl->xkb_state, index, - XKB_STATE_MODS_DEPRESSED)) + xkb_mod_index_t index = xkb_keymap_mod_get_index(s->xkb_keymap, mod_names[n]); + if (index != XKB_MOD_INVALID + && xkb_state_mod_index_is_active(s->xkb_state, index, + XKB_STATE_MODS_EFFECTIVE)) modifiers |= mods[n]; } return modifiers; } -static void get_shape_device(struct vo_wayland_state *wl) +static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 - if (!wl->cursor_shape_device && wl->cursor_shape_manager) { - wl->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager, - wl->pointer); + if (!s->cursor_shape_device && wl->cursor_shape_manager) { + s->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager, + s->pointer); } #endif } @@ -1616,8 +1808,17 @@ static void guess_focus(struct vo_wayland_state *wl) { // We can't actually know if the window is focused or not in wayland, // so just guess it with some common sense. Obviously won't work if - // the user has no keyboard. - if ((!wl->focused && wl->activated && wl->has_keyboard_input) || + // the user has no keyboard. We flag has_keyboard_input if + // at least one seat has it. + bool has_keyboard_input = false; + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + if (seat->has_keyboard_input) { + has_keyboard_input = true; + } + } + + if ((!wl->focused && wl->activated && has_keyboard_input) || (wl->focused && !wl->activated)) { wl->focused = !wl->focused; @@ -1672,12 +1873,10 @@ static int lookupkey(int key) return mpkey; } -static void prepare_resize(struct vo_wayland_state *wl, int width, int height) +static void prepare_resize(struct vo_wayland_state *wl) { - if (!width) - width = mp_rect_w(wl->geometry) / wl->scaling; - if (!height) - height = mp_rect_h(wl->geometry) / wl->scaling; + int32_t width = mp_rect_w(wl->geometry) / wl->scaling; + int32_t height = mp_rect_h(wl->geometry) / wl->scaling; xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, width, height); wl->pending_vo_events |= VO_EVENT_RESIZE; } @@ -1690,6 +1889,9 @@ static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode) static void rescale_geometry(struct vo_wayland_state *wl, double old_scale) { + if (!wl->vo_opts->hidpi_window_scale && !wl->locked_size) + return; + double factor = old_scale / wl->scaling; wl->window_size.x1 /= factor; wl->window_size.y1 /= factor; @@ -1734,6 +1936,37 @@ static void remove_output(struct vo_wayland_output *out) return; } +static void remove_seat(struct vo_wayland_seat *seat) +{ + if (!seat) + return; + + MP_VERBOSE(seat->wl, "Deregistering seat 0x%x\n", seat->id); + wl_list_remove(&seat->link); + if (seat == seat->wl->last_button_seat) + seat->wl->last_button_seat = NULL; + if (seat->keyboard) + wl_keyboard_destroy(seat->keyboard); + if (seat->pointer) + wl_pointer_destroy(seat->pointer); + if (seat->touch) + wl_touch_destroy(seat->touch); + if (seat->dnd_ddev) + wl_data_device_destroy(seat->dnd_ddev); +#if HAVE_WAYLAND_PROTOCOLS_1_32 + if (seat->cursor_shape_device) + wp_cursor_shape_device_v1_destroy(seat->cursor_shape_device); +#endif + if (seat->xkb_keymap) + xkb_keymap_unref(seat->xkb_keymap); + if (seat->xkb_state) + xkb_state_unref(seat->xkb_state); + + wl_seat_destroy(seat->seat); + talloc_free(seat); + return; +} + static void set_content_type(struct vo_wayland_state *wl) { if (!wl->content_type_manager) @@ -1748,20 +1981,23 @@ static void set_content_type(struct vo_wayland_state *wl) #endif } -static void set_cursor_shape(struct vo_wayland_state *wl) +static void set_cursor_shape(struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 - wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, wl->pointer_id, + wp_cursor_shape_device_v1_set_shape(s->cursor_shape_device, s->pointer_enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); #endif } -static int set_cursor_visibility(struct vo_wayland_state *wl, bool on) +static int set_cursor_visibility(struct vo_wayland_seat *s, bool on) { + if (!s) + return VO_FALSE; + struct vo_wayland_state *wl = s->wl; wl->cursor_visible = on; if (on) { - if (wl->cursor_shape_device) { - set_cursor_shape(wl); + if (s->cursor_shape_device) { + set_cursor_shape(s); } else { if (spawn_cursor(wl)) return VO_FALSE; @@ -1770,19 +2006,41 @@ static int set_cursor_visibility(struct vo_wayland_state *wl, bool on) if (!buffer) return VO_FALSE; int scale = MPMAX(wl->scaling, 1); - wl_pointer_set_cursor(wl->pointer, wl->pointer_id, wl->cursor_surface, + wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, wl->cursor_surface, img->hotspot_x / scale, img->hotspot_y / scale); - wl_surface_set_buffer_scale(wl->cursor_surface, scale); + wp_viewport_set_destination(wl->cursor_viewport, lround(img->width / scale), + lround(img->height / scale)); wl_surface_attach(wl->cursor_surface, buffer, 0, 0); wl_surface_damage_buffer(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); + wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, NULL, 0, 0); } return VO_TRUE; } +static int set_cursor_visibility_all_seats(struct vo_wayland_state *wl, bool on) +{ + bool unavailable = true; + bool failed = false; + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + if (seat->pointer) { + unavailable = false; + if (set_cursor_visibility(seat, on) == VO_FALSE) + failed = true; + } + } + + if (unavailable) + return VO_NOTAVAIL; + if (failed) + return VO_FALSE; + + return VO_TRUE; +} + static void set_geometry(struct vo_wayland_state *wl, bool resize) { struct vo *vo = wl->vo; @@ -1805,7 +2063,7 @@ static void set_geometry(struct vo_wayland_state *wl, bool resize) if (resize) { if (!wl->locked_size) wl->geometry = wl->window_size; - prepare_resize(wl, 0, 0); + prepare_resize(wl); } } @@ -1840,15 +2098,16 @@ static int set_screensaver_inhibitor(struct vo_wayland_state *wl, int state) static void set_surface_scaling(struct vo_wayland_state *wl) { - if (wl->fractional_scale_manager) + if (wl->scale_configured && (wl->fractional_scale_manager || + wl_surface_get_version(wl->surface) >= 6)) + { return; + } - // dmabuf_wayland is always wl->scaling = 1 double old_scale = wl->scaling; - wl->scaling = !wl->using_dmabuf_wayland ? wl->current_output->scale : 1; - + wl->scaling = wl->current_output->scale; rescale_geometry(wl, old_scale); - wl_surface_set_buffer_scale(wl->surface, wl->scaling); + wl->pending_vo_events |= VO_EVENT_DPI; } static void set_window_bounds(struct vo_wayland_state *wl) @@ -1862,22 +2121,34 @@ static void set_window_bounds(struct vo_wayland_state *wl) return; } + apply_keepaspect(wl, &wl->bounded_width, &wl->bounded_height); + if (wl->bounded_width && wl->bounded_width < wl->window_size.x1) wl->window_size.x1 = wl->bounded_width; if (wl->bounded_height && wl->bounded_height < wl->window_size.y1) wl->window_size.y1 = wl->bounded_height; } +static bool single_output_spanned(struct vo_wayland_state *wl) +{ + int outputs = 0; + struct vo_wayland_output *output; + wl_list_for_each(output, &wl->output_list, link) { + if (output->has_surface) + ++outputs; + if (outputs > 1) + return false; + } + return wl->current_output && outputs == 1; +} + static int spawn_cursor(struct vo_wayland_state *wl) { - /* Don't use this if we have cursor-shape. */ - if (wl->cursor_shape_device) - return 0; - /* Reuse if size is identical */ - if (!wl->pointer || wl->allocated_cursor_scale == wl->scaling) + if (wl->allocated_cursor_scale == wl->scaling) { return 0; - else if (wl->cursor_theme) + } else if (wl->cursor_theme) { wl_cursor_theme_destroy(wl->cursor_theme); + } const char *xcursor_theme = getenv("XCURSOR_THEME"); const char *size_str = getenv("XCURSOR_SIZE"); @@ -1896,9 +2167,12 @@ static int spawn_cursor(struct vo_wayland_state *wl) return 1; } - wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "left_ptr"); + wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "default"); + if (!wl->default_cursor) + 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"); + MP_ERR(wl, "Unable to get default and left_ptr XCursor from theme!\n"); return 1; } @@ -1909,9 +2183,6 @@ static int spawn_cursor(struct vo_wayland_state *wl) static void toggle_fullscreen(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; - wl->state_change = true; bool specific_screen = wl->vo_opts->fsscreen_id >= 0 || wl->vo_opts->fsscreen_name; if (wl->vo_opts->fullscreen && !specific_screen) { xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); @@ -1919,33 +2190,66 @@ static void toggle_fullscreen(struct vo_wayland_state *wl) struct vo_wayland_output *output = find_output(wl); xdg_toplevel_set_fullscreen(wl->xdg_toplevel, output->output); } else { + wl->state_change = wl->reconfigured; xdg_toplevel_unset_fullscreen(wl->xdg_toplevel); } } static void toggle_maximized(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; - wl->state_change = true; if (wl->vo_opts->window_maximized) { xdg_toplevel_set_maximized(wl->xdg_toplevel); } else { + wl->state_change = wl->reconfigured; xdg_toplevel_unset_maximized(wl->xdg_toplevel); } } static void update_app_id(struct vo_wayland_state *wl) { - if (!wl->xdg_toplevel) - return; xdg_toplevel_set_app_id(wl->xdg_toplevel, wl->vo_opts->appid); } +static void update_output_scaling(struct vo_wayland_state *wl) +{ + double old_scale = wl->scaling; + wl->scaling = wl->pending_scaling; + rescale_geometry(wl, old_scale); + set_geometry(wl, false); + prepare_resize(wl); + wl->need_rescale = false; + wl->pending_vo_events |= VO_EVENT_DPI; +} + +static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, + struct mp_rect old_output_geometry) +{ + if (wl->need_rescale) { + update_output_scaling(wl); + return; + } + + bool force_resize = false; + bool use_output_scale = wl_surface_get_version(wl->surface) < 6 && + !wl->fractional_scale_manager && + wl->scaling != wl->current_output->scale; + + if (use_output_scale) { + set_surface_scaling(wl); + force_resize = true; + } + + if (!mp_rect_equals(&old_output_geometry, &wl->current_output->geometry)) { + set_geometry(wl, false); + force_resize = true; + } + + if (!mp_rect_equals(&old_geometry, &wl->geometry) || force_resize) + prepare_resize(wl); +} + static int update_window_title(struct vo_wayland_state *wl, const char *title) { - if (!wl->xdg_toplevel) - return VO_NOTAVAIL; /* The xdg-shell protocol requires that the title is UTF-8. */ void *tmp = talloc_new(NULL); struct bstr b_title = bstr_sanitize_utf8_latin1(tmp, bstr0(title)); @@ -1954,12 +2258,6 @@ static int update_window_title(struct vo_wayland_state *wl, const char *title) return VO_TRUE; } -static void window_move(struct vo_wayland_state *wl, uint32_t serial) -{ - if (wl->xdg_toplevel) - xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); -} - static void wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int64_t timeout_ns) { if (wl->display_fd == -1) @@ -1994,6 +2292,18 @@ static void wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int64 wl_display_dispatch_pending(wl->display); } +static void begin_dragging(struct vo_wayland_state *wl) +{ + struct vo_wayland_seat *s = wl->last_button_seat; + if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && + !wl->locked_size && s) + { + xdg_toplevel_move(wl->xdg_toplevel, s->seat, s->pointer_button_serial); + wl->last_button_seat = NULL; + mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); + } +} + /* Non-static */ int vo_wayland_allocate_memfd(struct vo *vo, size_t size) { @@ -2076,8 +2386,6 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) set_input_region(wl, opts->cursor_passthrough); if (opt == &opts->fullscreen) toggle_fullscreen(wl); - if (opt == &opts->hidpi_window_scale) - set_geometry(wl, true); if (opt == &opts->window_maximized) toggle_maximized(wl); if (opt == &opts->window_minimized) @@ -2085,6 +2393,7 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) if (opt == &opts->geometry || opt == &opts->autofit || opt == &opts->autofit_smaller || opt == &opts->autofit_larger) { + wl->state_change = true; set_geometry(wl, true); } } @@ -2123,6 +2432,7 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) wl->window_size.x1 = s[0]; wl->window_size.y1 = s[1]; if (!wl->vo_opts->fullscreen && !wl->tiled) { + wl->state_change = true; if (wl->vo_opts->window_maximized) { xdg_toplevel_unset_maximized(wl->xdg_toplevel); wl_display_dispatch_pending(wl->display); @@ -2131,7 +2441,7 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) return VO_TRUE; } wl->geometry = wl->window_size; - prepare_resize(wl, 0, 0); + prepare_resize(wl); } return VO_TRUE; } @@ -2166,12 +2476,13 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) *(double *)arg = wl->scaling; return VO_TRUE; } + case VOCTRL_BEGIN_DRAGGING: + begin_dragging(wl); + return VO_TRUE; case VOCTRL_UPDATE_WINDOW_TITLE: return update_window_title(wl, (const char *)arg); case VOCTRL_SET_CURSOR_VISIBILITY: - if (!wl->pointer) - return VO_NOTAVAIL; - return set_cursor_visibility(wl, *(bool *)arg); + return set_cursor_visibility_all_seats(wl, *(bool *)arg); case VOCTRL_KILL_SCREENSAVER: return set_screensaver_inhibitor(wl, true); case VOCTRL_RESTORE_SCREENSAVER: @@ -2181,16 +2492,18 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) return VO_NOTIMPL; } -void vo_wayland_handle_fractional_scale(struct vo_wayland_state *wl) +void vo_wayland_handle_scale(struct vo_wayland_state *wl) { - if (wl->fractional_scale_manager && wl->viewport) - wp_viewport_set_destination(wl->viewport, - round(mp_rect_w(wl->geometry) / wl->scaling), - round(mp_rect_h(wl->geometry) / wl->scaling)); + wp_viewport_set_destination(wl->viewport, + lround(mp_rect_w(wl->geometry) / wl->scaling), + lround(mp_rect_h(wl->geometry) / wl->scaling)); } bool vo_wayland_init(struct vo *vo) { + if (!getenv("WAYLAND_DISPLAY")) + goto err; + vo->wl = talloc_zero(NULL, struct vo_wayland_state); struct vo_wayland_state *wl = vo->wl; @@ -2209,9 +2522,10 @@ bool vo_wayland_init(struct vo *vo) .vo_opts_cache = m_config_cache_alloc(wl, vo->global, &vo_sub_opts), }; wl->vo_opts = wl->vo_opts_cache->opts; - wl->using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland"); + bool using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland"); wl_list_init(&wl->output_list); + wl_list_init(&wl->seat_list); if (!wl->display) goto err; @@ -2243,6 +2557,12 @@ bool vo_wayland_init(struct vo *vo) goto err; } + if (!wl->viewporter) { + MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n", + wp_viewporter_interface.name); + goto err; + } + /* Can't be initialized during registry due to multi-protocol dependence */ if (create_viewports(wl)) goto err; @@ -2279,10 +2599,13 @@ bool vo_wayland_init(struct vo *vo) } #endif - if (wl->dnd_devman && wl->seat) { - 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 if (!wl->dnd_devman) { + if (wl->dnd_devman) { + struct vo_wayland_seat *seat; + wl_list_for_each(seat, &wl->seat_list, link) { + seat->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, seat->seat); + wl_data_device_add_listener(seat->dnd_ddev, &data_device_listener, seat); + } + } else { MP_VERBOSE(wl, "Compositor doesn't support the %s (ver. 3) protocol!\n", wl_data_device_manager_interface.name); } @@ -2325,7 +2648,7 @@ bool vo_wayland_init(struct vo *vo) update_app_id(wl); mp_make_wakeup_pipe(wl->wakeup_pipe); - wl->callback_surface = wl->using_dmabuf_wayland ? wl->video_surface : wl->surface; + wl->callback_surface = using_dmabuf_wayland ? wl->video_surface : wl->surface; wl->frame_callback = wl_surface_frame(wl->callback_surface); wl_callback_add_listener(wl->frame_callback, &frame_listener, wl); wl_surface_commit(wl->surface); @@ -2352,33 +2675,38 @@ bool vo_wayland_reconfig(struct vo *vo) if (!wl->current_output) return false; set_surface_scaling(wl); + wl->scale_configured = true; wl->pending_vo_events |= VO_EVENT_DPI; } - if (wl->vo_opts->auto_window_resize || !wl->configured) + if (wl->vo_opts->auto_window_resize || !wl->geometry_configured) set_geometry(wl, false); + if (wl->geometry_configured && wl->vo_opts->auto_window_resize) + wl->reconfigured = true; + if (wl->opts->configure_bounds) set_window_bounds(wl); - if (!wl->configured || !wl->locked_size) { - wl->geometry = wl->window_size; - wl->configured = true; - } - if (wl->vo_opts->cursor_passthrough) set_input_region(wl, true); - if (wl->vo_opts->fullscreen) - toggle_fullscreen(wl); + if (!wl->geometry_configured || !wl->locked_size) + wl->geometry = wl->window_size; - if (wl->vo_opts->window_maximized) - toggle_maximized(wl); + if (!wl->geometry_configured) { + if (wl->vo_opts->fullscreen) + toggle_fullscreen(wl); - if (wl->vo_opts->window_minimized) - do_minimize(wl); + if (wl->vo_opts->window_maximized) + toggle_maximized(wl); - prepare_resize(wl, 0, 0); + if (wl->vo_opts->window_minimized) + do_minimize(wl); + wl->geometry_configured = true; + } + + prepare_resize(wl); return true; } @@ -2403,6 +2731,10 @@ void vo_wayland_uninit(struct vo *vo) if (!wl) return; + if (wl->dnd_fd != -1) + close(wl->dnd_fd); + free_dnd_data(wl); + mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); if (wl->compositor) @@ -2412,9 +2744,6 @@ void vo_wayland_uninit(struct vo *vo) wl_subcompositor_destroy(wl->subcompositor); #if HAVE_WAYLAND_PROTOCOLS_1_32 - if (wl->cursor_shape_device) - wp_cursor_shape_device_v1_destroy(wl->cursor_shape_device); - if (wl->cursor_shape_manager) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape_manager); #endif @@ -2433,9 +2762,6 @@ void vo_wayland_uninit(struct vo *vo) wp_content_type_manager_v1_destroy(wl->content_type_manager); #endif - if (wl->dnd_ddev) - wl_data_device_destroy(wl->dnd_ddev); - if (wl->dnd_devman) wl_data_device_manager_destroy(wl->dnd_devman); @@ -2462,12 +2788,6 @@ void vo_wayland_uninit(struct vo *vo) if (wl->idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager); - if (wl->keyboard) - wl_keyboard_destroy(wl->keyboard); - - if (wl->pointer) - wl_pointer_destroy(wl->pointer); - if (wl->presentation) wp_presentation_destroy(wl->presentation); @@ -2480,6 +2800,9 @@ void vo_wayland_uninit(struct vo *vo) if (wl->viewport) wp_viewport_destroy(wl->viewport); + if (wl->cursor_viewport) + wp_viewport_destroy(wl->cursor_viewport); + if (wl->osd_viewport) wp_viewport_destroy(wl->osd_viewport); @@ -2492,9 +2815,6 @@ void vo_wayland_uninit(struct vo *vo) if (wl->dmabuf_feedback) zwp_linux_dmabuf_feedback_v1_destroy(wl->dmabuf_feedback); - if (wl->seat) - wl_seat_destroy(wl->seat); - if (wl->shm) wl_shm_destroy(wl->shm); @@ -2536,16 +2856,14 @@ void vo_wayland_uninit(struct vo *vo) if (wl->xkb_context) xkb_context_unref(wl->xkb_context); - if (wl->xkb_keymap) - xkb_keymap_unref(wl->xkb_keymap); - - if (wl->xkb_state) - xkb_state_unref(wl->xkb_state); - - struct vo_wayland_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &wl->output_list, link) + struct vo_wayland_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &wl->output_list, link) remove_output(output); + struct vo_wayland_seat *seat, *seat_tmp; + wl_list_for_each_safe(seat, seat_tmp, &wl->seat_list, link) + remove_seat(seat); + if (wl->display) wl_display_disconnect(wl->display); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index adbcca6..7a2f319 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -22,6 +22,8 @@ #include "input/event.h" #include "vo.h" +struct vo_wayland_seat; + typedef struct { uint32_t format; uint32_t padding; @@ -64,18 +66,18 @@ struct vo_wayland_state { int bounded_width; int reduced_height; int reduced_width; - int toplevel_width; - int toplevel_height; /* State */ bool activated; - bool configured; bool focused; bool frame_wait; - bool has_keyboard_input; + bool geometry_configured; bool hidden; bool initial_size_hint; bool locked_size; + bool need_rescale; + bool reconfigured; + bool scale_configured; bool state_change; bool tiled; bool toplevel_configured; @@ -83,6 +85,7 @@ struct vo_wayland_state { int mouse_x; int mouse_y; int pending_vo_events; + double pending_scaling; double scaling; int timeout_count; int wakeup_pipe[2]; @@ -96,7 +99,6 @@ struct vo_wayland_state { /* cursor-shape */ /* TODO: unvoid these if required wayland protocols is bumped to 1.32+ */ void *cursor_shape_manager; - void *cursor_shape_device; /* fractional-scale */ /* TODO: unvoid these if required wayland protocols is bumped to 1.31+ */ @@ -112,7 +114,6 @@ struct vo_wayland_state { struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback; wayland_format *format_map; uint32_t format_size; - bool using_dmabuf_wayland; /* presentation-time */ struct wp_presentation *presentation; @@ -138,26 +139,18 @@ struct vo_wayland_state { /* viewporter */ struct wp_viewporter *viewporter; struct wp_viewport *viewport; + struct wp_viewport *cursor_viewport; struct wp_viewport *osd_viewport; struct wp_viewport *video_viewport; /* Input */ - struct wl_keyboard *keyboard; - struct wl_pointer *pointer; - struct wl_seat *seat; - struct wl_touch *touch; + struct wl_list seat_list; struct xkb_context *xkb_context; - struct xkb_keymap *xkb_keymap; - struct xkb_state *xkb_state; - uint32_t keyboard_code; - int mpkey; - int mpmod; /* DND */ - struct wl_data_device *dnd_ddev; struct wl_data_device_manager *dnd_devman; struct wl_data_offer *dnd_offer; - enum mp_dnd_action dnd_action; + int dnd_action; // actually enum mp_dnd_action char *dnd_mime_type; int dnd_fd; int dnd_mime_score; @@ -168,7 +161,7 @@ struct vo_wayland_state { struct wl_surface *cursor_surface; bool cursor_visible; int allocated_cursor_scale; - uint32_t pointer_id; + struct vo_wayland_seat *last_button_seat; }; bool vo_wayland_check_visible(struct vo *vo); @@ -178,7 +171,7 @@ bool vo_wayland_reconfig(struct vo *vo); int vo_wayland_allocate_memfd(struct vo *vo, size_t size); int vo_wayland_control(struct vo *vo, int *events, int request, void *arg); -void vo_wayland_handle_fractional_scale(struct vo_wayland_state *wl); +void vo_wayland_handle_scale(struct vo_wayland_state *wl); void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, bool alpha); void vo_wayland_sync_swap(struct vo_wayland_state *wl); void vo_wayland_uninit(struct vo *vo); diff --git a/video/out/win32/droptarget.c b/video/out/win32/droptarget.c index 8a33522..fdf9c7a 100644 --- a/video/out/win32/droptarget.c +++ b/video/out/win32/droptarget.c @@ -18,6 +18,7 @@ #include #include +#include #include #include "common/msg.h" @@ -156,8 +157,10 @@ static STDMETHODIMP DropTarget_Drop(IDropTarget *self, IDataObject *pDataObj, wchar_t *buf = talloc_array(NULL, wchar_t, len + 1); if (DragQueryFileW(drop, i, buf, len + 1) == len) { - char *fname = mp_to_utf8(files, buf); + wchar_t *target = mp_w32_get_shell_link_target(buf); + char *fname = mp_to_utf8(files, target ? target : buf); files[recvd_files++] = fname; + talloc_free(target); MP_VERBOSE(t, "received dropped file: %s\n", fname); } else { diff --git a/video/out/win32/menu.c b/video/out/win32/menu.c new file mode 100644 index 0000000..25681e8 --- /dev/null +++ b/video/out/win32/menu.c @@ -0,0 +1,231 @@ +/* + * 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 . + */ + +#include +#include + +#include "libmpv/client.h" +#include "osdep/io.h" +#include "mpv_talloc.h" + +#include "menu.h" + +struct menu_ctx { + HMENU menu; + void *ta_data; // talloc context for MENUITEMINFOW.dwItemData +}; + +// append menu item to HMENU +static int append_menu(HMENU hmenu, UINT fMask, UINT fType, UINT fState, + wchar_t *title, HMENU submenu, void *data) +{ + static UINT id = WM_USER + 100; + MENUITEMINFOW mii = {0}; + + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_ID | fMask; + mii.wID = id++; + + if (fMask & MIIM_FTYPE) + mii.fType = fType; + if (fMask & MIIM_STATE) + mii.fState = fState; + if (fMask & MIIM_STRING) { + mii.dwTypeData = title; + mii.cch = wcslen(title); + } + if (fMask & MIIM_SUBMENU) + mii.hSubMenu = submenu; + if (fMask & MIIM_DATA) + mii.dwItemData = (ULONG_PTR)data; + + return InsertMenuItemW(hmenu, -1, TRUE, &mii) ? mii.wID : -1; +} + +// build fState for menu item creation +static int build_state(mpv_node *node) +{ + int fState = 0; + for (int i = 0; i < node->u.list->num; i++) { + mpv_node *item = &node->u.list->values[i]; + if (item->format != MPV_FORMAT_STRING) + continue; + + if (strcmp(item->u.string, "hidden") == 0) { + return -1; + } else if (strcmp(item->u.string, "checked") == 0) { + fState |= MFS_CHECKED; + } else if (strcmp(item->u.string, "disabled") == 0) { + fState |= MFS_DISABLED; + } + } + return fState; +} + +// build dwTypeData for menu item creation +static wchar_t *build_title(void *talloc_ctx, char *title, char *shortcut) +{ + if (shortcut && shortcut[0]) { + char *buf = talloc_asprintf(NULL, "%s\t%s", title, shortcut); + wchar_t *wbuf = mp_from_utf8(talloc_ctx, buf); + talloc_free(buf); + return wbuf; + } + return mp_from_utf8(talloc_ctx, title); +} + +// build HMENU from mpv node +// +// node structure: +// +// MPV_FORMAT_NODE_ARRAY +// MPV_FORMAT_NODE_MAP (menu item) +// "type" MPV_FORMAT_STRING +// "title" MPV_FORMAT_STRING +// "cmd" MPV_FORMAT_STRING +// "shortcut" MPV_FORMAT_STRING +// "state" MPV_FORMAT_NODE_ARRAY[MPV_FORMAT_STRING] +// "submenu" MPV_FORMAT_NODE_ARRAY[menu item] +static void build_menu(void *talloc_ctx, HMENU hmenu, struct mpv_node *node) +{ + if (node->format != MPV_FORMAT_NODE_ARRAY) + return; + + for (int i = 0; i < node->u.list->num; i++) { + mpv_node *item = &node->u.list->values[i]; + if (item->format != MPV_FORMAT_NODE_MAP) + continue; + + mpv_node_list *list = item->u.list; + + char *type = ""; + char *title = NULL; + char *cmd = NULL; + char *shortcut = NULL; + int fState = 0; + HMENU submenu = NULL; + + for (int j = 0; j < list->num; j++) { + char *key = list->keys[j]; + mpv_node *value = &list->values[j]; + + switch (value->format) { + case MPV_FORMAT_STRING: + if (strcmp(key, "title") == 0) { + title = value->u.string; + } else if (strcmp(key, "cmd") == 0) { + cmd = value->u.string; + } else if (strcmp(key, "type") == 0) { + type = value->u.string; + } else if (strcmp(key, "shortcut") == 0) { + shortcut = value->u.string; + } + break; + case MPV_FORMAT_NODE_ARRAY: + if (strcmp(key, "state") == 0) { + fState = build_state(value); + } else if (strcmp(key, "submenu") == 0) { + submenu = CreatePopupMenu(); + build_menu(talloc_ctx, submenu, value); + } + break; + default: + break; + } + } + + if (fState == -1) // hidden + continue; + + if (strcmp(type, "separator") == 0) { + append_menu(hmenu, MIIM_FTYPE, MFT_SEPARATOR, 0, NULL, NULL, NULL); + } else { + if (title == NULL || title[0] == '\0') + continue; + + UINT fMask = MIIM_STRING | MIIM_STATE; + bool grayed = false; + if (strcmp(type, "submenu") == 0) { + if (submenu == NULL) + submenu = CreatePopupMenu(); + fMask |= MIIM_SUBMENU; + grayed = GetMenuItemCount(submenu) == 0; + } else { + fMask |= MIIM_DATA; + grayed = cmd == NULL || cmd[0] == '\0' || cmd[0] == '#' || + strcmp(cmd, "ignore") == 0; + } + int id = append_menu(hmenu, fMask, 0, (UINT)fState, + build_title(talloc_ctx, title, shortcut), + submenu, talloc_strdup(talloc_ctx, cmd)); + if (id > 0 && grayed) + EnableMenuItem(hmenu, id, MF_BYCOMMAND | MF_GRAYED); + } + } +} + +struct menu_ctx *mp_win32_menu_init(void) +{ + struct menu_ctx *ctx = talloc_ptrtype(NULL, ctx); + ctx->menu = CreatePopupMenu(); + ctx->ta_data = talloc_new(ctx); + return ctx; +} + +void mp_win32_menu_uninit(struct menu_ctx *ctx) +{ + DestroyMenu(ctx->menu); + talloc_free(ctx); +} + +void mp_win32_menu_show(struct menu_ctx *ctx, HWND hwnd) +{ + POINT pt; + RECT rc; + + if (!GetCursorPos(&pt)) + return; + + GetClientRect(hwnd, &rc); + ScreenToClient(hwnd, &pt); + + if (!PtInRect(&rc, pt)) + return; + + ClientToScreen(hwnd, &pt); + TrackPopupMenuEx(ctx->menu, TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, + hwnd, NULL); +} + +void mp_win32_menu_update(struct menu_ctx *ctx, struct mpv_node *data) +{ + while (GetMenuItemCount(ctx->menu) > 0) + RemoveMenu(ctx->menu, 0, MF_BYPOSITION); + talloc_free_children(ctx->ta_data); + + build_menu(ctx->ta_data, ctx->menu, data); +} + +const char* mp_win32_menu_get_cmd(struct menu_ctx *ctx, UINT id) +{ + MENUITEMINFOW mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_DATA; + + GetMenuItemInfoW(ctx->menu, id, FALSE, &mii); + return (const char *)mii.dwItemData; +} diff --git a/video/out/win32/menu.h b/video/out/win32/menu.h new file mode 100644 index 0000000..8b1fe72 --- /dev/null +++ b/video/out/win32/menu.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef MP_WIN32_MENU_H +#define MP_WIN32_MENU_H + +#include + +struct mpv_node; +struct menu_ctx; + +struct menu_ctx *mp_win32_menu_init(void); +void mp_win32_menu_uninit(struct menu_ctx *ctx); +void mp_win32_menu_show(struct menu_ctx *ctx, HWND hwnd); +void mp_win32_menu_update(struct menu_ctx *ctx, struct mpv_node *data); +const char* mp_win32_menu_get_cmd(struct menu_ctx *ctx, UINT id); + +#endif diff --git a/video/out/x11_common.c b/video/out/x11_common.c index b4605bf..fa2f2ba 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -98,7 +99,9 @@ #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) -#define MWM_DECOR_ALL (1L << 0) +// Equals to all MWM_DECOR_* OR'd together. +#define MWM_DECOR_ALL 126 +#define MWM_DECOR_TITLE (1L << 3) typedef struct { @@ -603,6 +606,70 @@ static void vo_x11_get_bounding_monitors(struct vo_x11_state *x11, long b[4]) } } +// Get the dpi scale of the x11 screen. Almost no GUI programs use this value +// nowadays, and it has inconsistent behavior for different drivers. +// (it returns the physical display DPI for proprietary NVIDIA driver only, +// but essentially a user-set prefrence value everywhere else) +static void vo_x11_get_x11_screen_dpi_scale(struct vo_x11_state *x11) +{ + int w_mm = DisplayWidthMM(x11->display, x11->screen); + int h_mm = DisplayHeightMM(x11->display, x11->screen); + double dpi_x = x11->ws_width * 25.4 / w_mm; + double dpi_y = x11->ws_height * 25.4 / h_mm; + double base_dpi = 96; + if (isfinite(dpi_x) && isfinite(dpi_y)) { + int s_x = lrint(MPCLAMP(2 * dpi_x / base_dpi, 0, 20)); + int s_y = lrint(MPCLAMP(2 * dpi_y / base_dpi, 0, 20)); + if (s_x == s_y && s_x > 2 && s_x < 20) { + x11->dpi_scale = s_x / 2.0; + MP_VERBOSE(x11, "Using X11 screen DPI scale: %g", x11->dpi_scale); + } + } +} + +// Get the dpi scale from the Xft.dpi resource. In practice, this value is much more +// commonly used by GUI programs for scaling compared to the x11 screen dpi. +// This is always a preference value so it's also more consistent. +static bool vo_x11_get_xft_dpi_scale(struct vo_x11_state *x11) +{ + XrmInitialize(); + char *resman = XResourceManagerString(x11->display); + if (!resman) + return false; + + XrmDatabase db = XrmGetStringDatabase(resman); + if (!db) + return false; + + XrmValue ret; + char *type; + double base_dpi = 96; + bool success = false; + if (XrmGetResource(db, "Xft.dpi", "String", &type, &ret) == True && + ret.addr && !strcmp("String", type)) + { + char *end; + long value = strtol(ret.addr, &end, 10); + if (*ret.addr && *end == '\0') { + int s = lrint(MPCLAMP(2 * value / base_dpi, 0, 20)); + if (s > 2 && s < 20) { + x11->dpi_scale = s / 2.0; + MP_VERBOSE(x11, "Using Xft.dpi scale: %g", x11->dpi_scale); + success = true; + } + } + } + XrmDestroyDatabase(db); + return success; +} + +static void vo_x11_get_dpi_scale(struct vo_x11_state *x11) +{ + if (!vo_x11_get_xft_dpi_scale(x11)) + vo_x11_get_x11_screen_dpi_scale(x11); + x11->pending_vo_events |= VO_EVENT_DPI; +} + bool vo_x11_init(struct vo *vo) { char *dispName; @@ -667,21 +734,7 @@ bool vo_x11_init(struct vo *vo) x11->ws_width, x11->ws_height, dispName, x11->display_is_local ? "local" : "remote"); - int w_mm = DisplayWidthMM(x11->display, x11->screen); - int h_mm = DisplayHeightMM(x11->display, x11->screen); - double dpi_x = x11->ws_width * 25.4 / w_mm; - double dpi_y = x11->ws_height * 25.4 / h_mm; - double base_dpi = 96; - if (isfinite(dpi_x) && isfinite(dpi_y) && x11->opts->hidpi_window_scale) { - int s_x = lrint(MPCLAMP(dpi_x / base_dpi, 0, 10)); - int s_y = lrint(MPCLAMP(dpi_y / base_dpi, 0, 10)); - if (s_x == s_y && s_x > 1 && s_x < 10) { - x11->dpi_scale = s_x; - MP_VERBOSE(x11, "Assuming DPI scale %d for prescaling. This can " - "be disabled with --hidpi-window-scale=no.\n", - x11->dpi_scale); - } - } + vo_x11_get_dpi_scale(x11); x11->wm_type = vo_wm_detect(vo); @@ -754,7 +807,8 @@ static const struct mp_keymap keymap[] = { {XF86XK_HomePage, MP_KEY_HOMEPAGE}, {XF86XK_WWW, MP_KEY_WWW}, {XF86XK_Mail, MP_KEY_MAIL}, {XF86XK_Favorites, MP_KEY_FAVORITES}, {XF86XK_Search, MP_KEY_SEARCH}, {XF86XK_Sleep, MP_KEY_SLEEP}, - {XF86XK_Back, MP_KEY_BACK}, {XF86XK_Tools, MP_KEY_TOOLS}, + {XF86XK_Back, MP_KEY_GO_BACK}, {XF86XK_Forward, MP_KEY_GO_FORWARD}, + {XF86XK_Tools, MP_KEY_TOOLS}, {XF86XK_ZoomIn, MP_KEY_ZOOMIN}, {XF86XK_ZoomOut, MP_KEY_ZOOMOUT}, {0, 0} @@ -783,7 +837,7 @@ static int vo_x11_lookupkey(int key) return mpkey; } -static void vo_x11_decoration(struct vo *vo, bool d) +static void vo_x11_decoration(struct vo *vo, bool decorations, bool title_bar) { struct vo_x11_state *x11 = vo->x11; @@ -794,8 +848,9 @@ static void vo_x11_decoration(struct vo *vo, bool d) MotifWmHints mhints = {0}; bool got = x11_get_property_copy(x11, x11->window, motif_hints, motif_hints, 32, &mhints, sizeof(mhints)); - // hints weren't set, and decorations requested -> assume WM displays them - if (!got && d) + // If hints weren't set, and decorations and title bar requested, + // assume WM displays them. + if (!got && decorations && title_bar) return; if (!got) { mhints.flags = MWM_HINTS_FUNCTIONS; @@ -803,7 +858,8 @@ static void vo_x11_decoration(struct vo *vo, bool d) MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; } mhints.flags |= MWM_HINTS_DECORATIONS; - mhints.decorations = d ? MWM_DECOR_ALL : 0; + mhints.decorations = decorations ? MWM_DECOR_ALL : 0; + mhints.decorations &= ~(!title_bar ? MWM_DECOR_TITLE : 0); XChangeProperty(x11->display, x11->window, motif_hints, motif_hints, 32, PropModeReplace, (unsigned char *) &mhints, 5); } @@ -1105,11 +1161,6 @@ static void vo_x11_check_net_wm_state_change(struct vo *vo) XFree(elems); } - if (opts->window_maximized && !is_maximized && x11->geometry_change) { - x11->geometry_change = false; - vo_x11_config_vo_window(vo); - } - opts->window_minimized = is_minimized; x11->hidden = is_minimized; m_config_cache_write_opt(x11->opts_cache, &opts->window_minimized); @@ -1163,7 +1214,28 @@ static void release_all_keys(struct vo *vo) if (x11->no_autorepeat) mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - x11->win_drag_button1_down = false; +} + +static void vo_x11_begin_dragging(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + XEvent Event = x11->last_button_event; + if (Event.type == ButtonPress && !x11->fs && + !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y)) + { + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); + XUngrabPointer(x11->display, CurrentTime); + + long params[5] = { + Event.xmotion.x_root, Event.xmotion.y_root, + 8, // _NET_WM_MOVERESIZE_MOVE + Event.xbutton.button, + 1, // source indication: normal + }; + x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); + x11->last_button_event = (XEvent){0}; + } } void vo_x11_check_events(struct vo *vo) @@ -1176,6 +1248,8 @@ void vo_x11_check_events(struct vo *vo) while (XPending(display)) { XNextEvent(display, &Event); + if (XFilterEvent(&Event, x11->window)) + continue; MP_TRACE(x11, "XEvent: %d\n", Event.type); switch (Event.type) { case Expose: @@ -1232,30 +1306,12 @@ void vo_x11_check_events(struct vo *vo) release_all_keys(vo); break; case MotionNotify: - if (x11->win_drag_button1_down && !x11->fs && - !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, - Event.xmotion.y)) - { - mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - XUngrabPointer(x11->display, CurrentTime); - - long params[5] = { - Event.xmotion.x_root, Event.xmotion.y_root, - 8, // _NET_WM_MOVERESIZE_MOVE - 1, // button 1 - 1, // source indication: normal - }; - x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); - } else { - mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, - Event.xmotion.y); - } - x11->win_drag_button1_down = false; + mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y); break; case LeaveNotify: if (Event.xcrossing.mode != NotifyNormal) break; - x11->win_drag_button1_down = false; mp_input_put_key(x11->input_ctx, MP_KEY_MOUSE_LEAVE); break; case EnterNotify: @@ -1266,22 +1322,20 @@ void vo_x11_check_events(struct vo *vo) case ButtonPress: if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT) break; - if (Event.xbutton.button == 1) - x11->win_drag_button1_down = true; mp_input_put_key(x11->input_ctx, (MP_MBTN_BASE + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_DOWN); long msg[4] = {XEMBED_REQUEST_FOCUS}; vo_x11_xembed_send_message(x11, msg); + x11->last_button_event = Event; break; case ButtonRelease: if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT) break; - if (Event.xbutton.button == 1) - x11->win_drag_button1_down = false; mp_input_put_key(x11->input_ctx, (MP_MBTN_BASE + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_UP); + x11->last_button_event = Event; break; case MapNotify: x11->window_hidden = false; @@ -1343,6 +1397,7 @@ void vo_x11_check_events(struct vo *vo) } if (Event.type == x11->xrandr_event) { xrandr_read(x11); + vo_x11_get_dpi_scale(x11); vo_x11_update_geometry(vo); } break; @@ -1372,8 +1427,7 @@ static void vo_x11_sizehint(struct vo *vo, struct mp_rect rc, bool override_pos) override_pos; // for fullscreen and such XSizeHints *hint = XAllocSizeHints(); - if (!hint) - return; // OOM + MP_HANDLE_OOM(hint); hint->flags |= PSize | (force_pos ? PPosition : 0); hint->x = rc.x0; @@ -1579,7 +1633,7 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis, if (x11->xim) { x11->xic = XCreateIC(x11->xim, - XNInputStyle, XIMPreeditNone | XIMStatusNone, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, x11->window, XNFocusWindow, x11->window, NULL); @@ -1601,7 +1655,7 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) struct vo_x11_state *x11 = vo->x11; vo_x11_move_resize(vo, true, true, rc); - vo_x11_decoration(vo, x11->opts->border); + vo_x11_decoration(vo, x11->opts->border, x11->opts->title_bar); if (x11->opts->fullscreen && (x11->wm_type & vo_wm_FULLSCREEN)) { Atom state = XA(x11, _NET_WM_STATE_FULLSCREEN); @@ -1667,12 +1721,12 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) vo_x11_xembed_update(x11, XEMBED_MAPPED); } -static void vo_x11_highlevel_resize(struct vo *vo, struct mp_rect rc) +static void vo_x11_highlevel_resize(struct vo *vo, struct mp_rect rc, bool force) { struct vo_x11_state *x11 = vo->x11; struct mp_vo_opts *opts = x11->opts; - bool reset_pos = opts->force_window_position; + bool reset_pos = opts->force_window_position || force; if (reset_pos) { x11->nofsrc = rc; } else { @@ -1742,10 +1796,6 @@ void vo_x11_config_vo_window(struct vo *vo) assert(x11->window); - // Don't attempt to change autofit/geometry on maximized windows. - if (x11->geometry_change && opts->window_maximized) - return; - vo_x11_update_screeninfo(vo); struct vo_win_geometry geo; @@ -1761,15 +1811,19 @@ void vo_x11_config_vo_window(struct vo *vo) bool reset_size = (x11->old_dw != RC_W(rc) || x11->old_dh != RC_H(rc)) && (opts->auto_window_resize || x11->geometry_change); + reset_size |= (x11->old_x != rc.x0 || x11->old_y != rc.y0) && + (x11->geometry_change); x11->old_dw = RC_W(rc); x11->old_dh = RC_H(rc); + x11->old_x = rc.x0; + x11->old_y = rc.y0; if (x11->window_hidden) { x11->nofsrc = rc; vo_x11_map_window(vo, rc); } else if (reset_size) { - vo_x11_highlevel_resize(vo, rc); + vo_x11_highlevel_resize(vo, rc, x11->geometry_change); } x11->geometry_change = false; @@ -1922,7 +1976,7 @@ static void vo_x11_fullscreen(struct vo *vo) rc = x11->screenrc; } - vo_x11_decoration(vo, opts->border && !x11->fs); + vo_x11_decoration(vo, opts->border && !x11->fs, opts->title_bar); vo_x11_sizehint(vo, rc, true); XMoveResizeWindow(x11->display, x11->window, rc.x0, rc.y0, @@ -2020,8 +2074,8 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) vo_x11_fullscreen(vo); if (opt == &opts->ontop) vo_x11_setlayer(vo, opts->ontop); - if (opt == &opts->border) - vo_x11_decoration(vo, opts->border); + if (opt == &opts->border || opt == &opts->title_bar) + vo_x11_decoration(vo, opts->border, opts->title_bar); if (opt == &opts->all_workspaces) vo_x11_sticky(vo, opts->all_workspaces); if (opt == &opts->window_minimized) @@ -2035,6 +2089,12 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) if (opt == &opts->geometry || opt == &opts->autofit || opt == &opts->autofit_smaller || opt == &opts->autofit_larger) { + if (opts->window_maximized && !opts->fullscreen) { + x11->opts->window_maximized = false; + m_config_cache_write_opt(x11->opts_cache, + &x11->opts->window_maximized); + vo_x11_maximize(vo); + } vo_x11_set_geometry(vo); } } @@ -2044,16 +2104,16 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) int *s = arg; if (!x11->window || x11->parent) return VO_FALSE; - s[0] = (x11->fs ? RC_W(x11->nofsrc) : RC_W(x11->winrc)) / x11->dpi_scale; - s[1] = (x11->fs ? RC_H(x11->nofsrc) : RC_H(x11->winrc)) / x11->dpi_scale; + s[0] = x11->fs ? RC_W(x11->nofsrc) : RC_W(x11->winrc); + s[1] = x11->fs ? RC_H(x11->nofsrc) : RC_H(x11->winrc); return VO_TRUE; } case VOCTRL_SET_UNFS_WINDOW_SIZE: { int *s = arg; if (!x11->window || x11->parent) return VO_FALSE; - int w = s[0] * x11->dpi_scale; - int h = s[1] * x11->dpi_scale; + int w = s[0]; + int h = s[1]; struct mp_rect rc = x11->winrc; rc.x1 = rc.x0 + w; rc.y1 = rc.y0 + h; @@ -2063,7 +2123,7 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) &x11->opts->window_maximized); vo_x11_maximize(vo); } - vo_x11_highlevel_resize(vo, rc); + vo_x11_highlevel_resize(vo, rc, false); if (!x11->fs) { // guess new window size, instead of waiting for X x11->winrc.x1 = x11->winrc.x0 + w; x11->winrc.y1 = x11->winrc.y0 + h; @@ -2151,6 +2211,9 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg) case VOCTRL_GET_HIDPI_SCALE: *(double *)arg = x11->dpi_scale; return VO_TRUE; + case VOCTRL_BEGIN_DRAGGING: + vo_x11_begin_dragging(vo); + return VO_TRUE; } return VO_NOTIMPL; } diff --git a/video/out/x11_common.h b/video/out/x11_common.h index 62a96d7..b8ebfe0 100644 --- a/video/out/x11_common.h +++ b/video/out/x11_common.h @@ -61,7 +61,7 @@ struct vo_x11_state { int display_is_local; int ws_width; int ws_height; - int dpi_scale; + double dpi_scale; struct mp_rect screenrc; char *window_title; @@ -114,6 +114,7 @@ struct vo_x11_state { * stays the same (even if that size is different from the current * window size after the user modified the latter). */ int old_dw, old_dh; + int old_x, old_y; /* Video size changed during fullscreen when we couldn't tell the new * size to the window manager. Must set window size when turning * fullscreen off. */ @@ -137,10 +138,9 @@ struct vo_x11_state { Atom dnd_requested_action; Window dnd_src_window; - /* dragging the window */ - bool win_drag_button1_down; - Atom icc_profile_property; + + XEvent last_button_event; }; bool vo_x11_init(struct vo *vo); -- cgit v1.2.3